react-wizard-engine 0.1.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,2242 @@
1
+ "use client";
2
+
3
+ // src/enums/wizard-event-type.enum.ts
4
+ var WizardEventType = /* @__PURE__ */ ((WizardEventType2) => {
5
+ WizardEventType2["Complete"] = "complete";
6
+ WizardEventType2["Exit"] = "exit";
7
+ WizardEventType2["NavigationCancelled"] = "navigation_cancelled";
8
+ WizardEventType2["NavigationEnd"] = "navigation_end";
9
+ WizardEventType2["NavigationError"] = "navigation_error";
10
+ WizardEventType2["NavigationIgnored"] = "navigation_ignored";
11
+ WizardEventType2["NavigationStart"] = "navigation_start";
12
+ WizardEventType2["ResolveEnd"] = "resolve_end";
13
+ WizardEventType2["ResolveStart"] = "resolve_start";
14
+ WizardEventType2["ScrollEnd"] = "scroll_end";
15
+ WizardEventType2["ScrollStart"] = "scroll_start";
16
+ WizardEventType2["StepHide"] = "step_hide";
17
+ WizardEventType2["StepShow"] = "step_show";
18
+ return WizardEventType2;
19
+ })(WizardEventType || {});
20
+
21
+ // src/core/utils/general/last.ts
22
+ var last = (arr) => arr[arr.length - 1];
23
+
24
+ // src/core/utils/general/to-array.ts
25
+ var toArray = (arr) => Array.isArray(arr) ? arr : [arr];
26
+
27
+ // src/core/utils/state/get-step-id.ts
28
+ var getStepId = (stepState) => {
29
+ const branchesId = stepState.branches.join(",");
30
+ return `${stepState.htmlIndex}:${branchesId}.${stepState.categoryId}.${stepState.id}`;
31
+ };
32
+ var getStepStateId = (stepState) => {
33
+ return `${+stepState.isShow}${+stepState.isCompleted}${+stepState.isSkipped}${+stepState.isActive}`;
34
+ };
35
+
36
+ // src/core/utils/state/get-shape-id.ts
37
+ var getShapeId = (state, prefix = "") => {
38
+ const id = state.map(getStepId).join("|");
39
+ return prefix !== "" && prefix !== void 0 ? `@${prefix}@${id}` : id;
40
+ };
41
+
42
+ // src/core/utils/state/get-state-id.ts
43
+ var getStateId = (state, prefix = "") => {
44
+ const id = state.map(getStepStateId).join("|");
45
+ return prefix ? `@${prefix}@${id}` : id;
46
+ };
47
+
48
+ // src/core/builders/build-tree-state.ts
49
+ function buildWizardBranchState(id, categories) {
50
+ const shownCategories = [];
51
+ const activeCategories = [];
52
+ const completedCategories = [];
53
+ const skippedCategories = [];
54
+ const passedCategories = [];
55
+ for (const category of categories) {
56
+ if (category.isActive) activeCategories.push(category);
57
+ if (category.isShow) shownCategories.push(category);
58
+ if (category.isCompleted) completedCategories.push(category);
59
+ if (category.isSkipped) skippedCategories.push(category);
60
+ if (category.isPassed) passedCategories.push(category);
61
+ }
62
+ const activeCategory = activeCategories[0] ?? shownCategories[0];
63
+ const getShownCategoryIndex = (category) => shownCategories.indexOf(category);
64
+ const activeCategoryIndex = getShownCategoryIndex(activeCategory);
65
+ const firstCategory = shownCategories[0];
66
+ const lastCategory = last(shownCategories);
67
+ const prevCategory = activeCategoryIndex > 0 ? shownCategories[activeCategoryIndex - 1] ?? null : null;
68
+ const nextCategory = activeCategoryIndex >= 0 ? shownCategories[activeCategoryIndex + 1] ?? null : null;
69
+ const getShownCategories = (fromIndex, toIndex) => shownCategories.slice(fromIndex, toIndex + 1);
70
+ return {
71
+ activeCategories,
72
+ activeCategory,
73
+ activeCategoryIndex,
74
+ categories: [...categories],
75
+ completedCategories,
76
+ firstCategory,
77
+ getShownCategories,
78
+ getShownCategoryIndex,
79
+ id,
80
+ lastCategory,
81
+ nextCategory,
82
+ passedCategories,
83
+ prevCategory,
84
+ shownCategories,
85
+ skippedCategories
86
+ };
87
+ }
88
+ function buildWizardCategoryState(id, htmlIndex, steps) {
89
+ const branches = steps[0]?.branches ?? [];
90
+ const shownSteps = [];
91
+ const activeSteps = [];
92
+ const completedSteps = [];
93
+ const skippedSteps = [];
94
+ const passedSteps = [];
95
+ for (const step of steps) {
96
+ if (step.isActive) activeSteps.push(step);
97
+ if (step.isShow) shownSteps.push(step);
98
+ if (step.isSkipped) skippedSteps.push(step);
99
+ if (step.isCompleted) completedSteps.push(step);
100
+ if (step.isSkipped || step.isCompleted) passedSteps.push(step);
101
+ }
102
+ const isActive = activeSteps.length > 0;
103
+ const isShow = shownSteps.length > 0;
104
+ const skippedAt = shownSteps.findIndex((step) => step.isSkipped);
105
+ const isSkipped = skippedAt !== -1;
106
+ const completedShownSteps = shownSteps.filter((step) => step.isCompleted || step.isSkipped);
107
+ const isCompleted = completedShownSteps.length === shownSteps.length && shownSteps.length > 0;
108
+ const isPassed = isCompleted || isSkipped;
109
+ const lastActiveStep = last(activeSteps);
110
+ const activeStepIndex = lastActiveStep ? shownSteps.indexOf(lastActiveStep) : -1;
111
+ const firstStep = shownSteps[0];
112
+ const lastStep = last(shownSteps);
113
+ const firstActiveStep = activeSteps[0];
114
+ const prevStep = activeStepIndex > 0 ? shownSteps[activeStepIndex - 1] ?? null : null;
115
+ const nextStep = activeStepIndex >= 0 ? shownSteps[activeStepIndex + 1] ?? null : null;
116
+ return {
117
+ activeStepIndex,
118
+ activeSteps,
119
+ branches: [...branches],
120
+ completedSteps,
121
+ firstActiveStep,
122
+ firstStep,
123
+ htmlIndex,
124
+ id,
125
+ isActive,
126
+ isCompleted,
127
+ isPassed,
128
+ isShow,
129
+ isSkipped,
130
+ lastActiveStep,
131
+ lastStep,
132
+ nextStep,
133
+ passedSteps,
134
+ prevStep,
135
+ shownSteps,
136
+ skippedAt,
137
+ skippedSteps,
138
+ steps: [...steps]
139
+ };
140
+ }
141
+ function buildWizardTreeState(state, activeBranchId) {
142
+ const stepMap = {};
143
+ const categoryOrder = [];
144
+ const stepsByCategory = {};
145
+ for (const step of state) {
146
+ stepMap[step.id] = step;
147
+ if (!stepsByCategory[step.categoryId]) {
148
+ categoryOrder.push(step.categoryId);
149
+ stepsByCategory[step.categoryId] = [];
150
+ }
151
+ stepsByCategory[step.categoryId].push(step);
152
+ }
153
+ const categoryMap = {};
154
+ const categoriesByBranch = {};
155
+ for (const [index, categoryId] of categoryOrder.entries()) {
156
+ const categoryState = buildWizardCategoryState(
157
+ categoryId,
158
+ index,
159
+ stepsByCategory[categoryId]
160
+ );
161
+ categoryMap[categoryId] = categoryState;
162
+ for (const branchId of categoryState.branches) {
163
+ (categoriesByBranch[branchId] ??= []).push(categoryState);
164
+ }
165
+ }
166
+ const branchMap = {};
167
+ const branches = [];
168
+ for (const branchId of Object.keys(categoriesByBranch)) {
169
+ const branchState = buildWizardBranchState(branchId, categoriesByBranch[branchId]);
170
+ branchMap[branchId] = branchState;
171
+ branches.push(branchState);
172
+ }
173
+ const activeBranch = pickActiveBranch(branches, branchMap, activeBranchId);
174
+ const activeCategory = activeBranch.activeCategory;
175
+ const activeSteps = activeCategory.activeSteps;
176
+ const lastActiveStep = last(activeSteps);
177
+ return {
178
+ activeBranch,
179
+ activeCategory,
180
+ activeSteps,
181
+ branches,
182
+ branchMap,
183
+ categoryMap,
184
+ id: getStateId(state),
185
+ lastActiveStep,
186
+ state: [...state],
187
+ stepMap
188
+ };
189
+ }
190
+ function pickActiveBranch(branches, branchMap, activeBranchId) {
191
+ if (branches.length === 1) {
192
+ return branches[0];
193
+ }
194
+ const activeBranches = branches.filter((branch) => branch.activeCategories.length > 0);
195
+ if (activeBranches.length === 1) {
196
+ return activeBranches[0];
197
+ }
198
+ if (activeBranchId && branchMap[activeBranchId]) {
199
+ return branchMap[activeBranchId];
200
+ }
201
+ return activeBranches[0] ?? branches[0];
202
+ }
203
+
204
+ // src/core/builders/wizard-default.const.ts
205
+ var wizardDefaultState = {
206
+ isActive: false,
207
+ isCompleted: false,
208
+ isShow: true,
209
+ isSkipped: false
210
+ };
211
+ var wizardDefaultBranch = "main";
212
+
213
+ // src/core/errors/wizard.error.ts
214
+ var WizardError = class extends Error {
215
+ name = "WizardError";
216
+ constructor(message) {
217
+ super(message);
218
+ Object.setPrototypeOf(this, new.target.prototype);
219
+ }
220
+ };
221
+ var WizardInitializationError = class extends WizardError {
222
+ name = "WizardInitializationError";
223
+ };
224
+ var WizardNavigationError = class extends WizardError {
225
+ name = "WizardNavigationError";
226
+ };
227
+ var WizardResolverError = class extends WizardError {
228
+ name = "WizardResolverError";
229
+ };
230
+
231
+ // src/core/rules/wizard-rule.ts
232
+ var WizardRule = class {
233
+ };
234
+
235
+ // src/core/rules/wizard-active-step.rule.ts
236
+ var WizardActiveStepRule = class extends WizardRule {
237
+ verify({ lastActiveStep }) {
238
+ if (!lastActiveStep) {
239
+ throw new WizardError("[WizardActiveStepRule]: There are no available active step in tree state.");
240
+ }
241
+ }
242
+ };
243
+
244
+ // src/core/rules/wizard-passed-prev-categories.rule.ts
245
+ var WizardPassedPrevCategoriesRule = class extends WizardRule {
246
+ verify({ activeBranch, activeCategory }) {
247
+ const prevCategories = activeBranch.shownCategories.slice(0, activeBranch.activeCategoryIndex);
248
+ const hasUnpassedPrevCategories = prevCategories.some((prevCategory) => !prevCategory.isPassed);
249
+ if (hasUnpassedPrevCategories) {
250
+ const prevCategoryNames = prevCategories.map((prevCategory) => prevCategory.id).join(",");
251
+ throw new WizardError(
252
+ `[WizardPassedPrevCategoriesRule]: Active category "${activeCategory.id}" has neither completed nor skipped prev categories. Please, skip or complete categories: "${prevCategoryNames}".`
253
+ );
254
+ }
255
+ }
256
+ };
257
+
258
+ // src/core/rules/wizard-passed-prev-steps.rule.ts
259
+ var WizardPassedPrevStepsRule = class extends WizardRule {
260
+ verify({ activeCategory }) {
261
+ const prevSteps = activeCategory.shownSteps.slice(0, activeCategory.activeStepIndex);
262
+ const prevUnpassedSteps = prevSteps.filter((prevStep) => !prevStep.isCompleted && !prevStep.isSkipped);
263
+ if (prevUnpassedSteps.length) {
264
+ const prevStepNames = prevUnpassedSteps.map((prevStep) => prevStep.id).join(",");
265
+ throw new WizardError(
266
+ `[WizardPassedPrevStepsRule]: Last active step "${activeCategory.lastActiveStep.id}" has not completed steps before. Please, complete or hide steps: "${prevStepNames}".`
267
+ );
268
+ }
269
+ }
270
+ };
271
+
272
+ // src/core/rules/wizard-shown-active-category.rule.ts
273
+ var WizardShownActiveCategoryRule = class extends WizardRule {
274
+ verify({ activeCategory }) {
275
+ if (!activeCategory.isShow) {
276
+ throw new WizardError(
277
+ `[WizardShownActiveCategoryRule]: Active category is hidden. Please, show category "${activeCategory.id}" to be able to activate it.`
278
+ );
279
+ }
280
+ }
281
+ };
282
+
283
+ // src/core/rules/wizard-single-active-category.rule.ts
284
+ var WizardSingleActiveCategoryRule = class extends WizardRule {
285
+ verify({ activeBranch }) {
286
+ if (activeBranch.activeCategories.length > 1) {
287
+ const activeCategoryNames = activeBranch.activeCategories.map((c) => c.id).join(",");
288
+ throw new WizardError(
289
+ `[WizardSingleActiveCategoryRule]: There are 2 active categories at the same time: ${activeCategoryNames}.`
290
+ );
291
+ }
292
+ }
293
+ };
294
+
295
+ // src/core/rules/wizard-rules.ts
296
+ var wizardRules = [
297
+ new WizardActiveStepRule(),
298
+ new WizardSingleActiveCategoryRule(),
299
+ new WizardShownActiveCategoryRule(),
300
+ new WizardPassedPrevStepsRule(),
301
+ new WizardPassedPrevCategoriesRule()
302
+ ];
303
+
304
+ // src/core/state/wizard-tree.state.ts
305
+ var WizardCompleteTreeState = class {
306
+ constructor(originTree) {
307
+ this.originTree = originTree;
308
+ }
309
+ originTree;
310
+ state = "complete";
311
+ };
312
+ var WizardExitTreeState = class {
313
+ constructor(originTree) {
314
+ this.originTree = originTree;
315
+ }
316
+ originTree;
317
+ state = "exit";
318
+ };
319
+
320
+ // src/core/state/project-to-tree.ts
321
+ var projectToTree = (target) => target instanceof WizardExitTreeState || target instanceof WizardCompleteTreeState ? target.originTree : target;
322
+
323
+ // src/core/builders/wizard-tree-state.builder.ts
324
+ var WizardTreeStateBuilder = class _WizardTreeStateBuilder {
325
+ branch;
326
+ state;
327
+ constructor(treeState, branch) {
328
+ this.state = treeState.state.map((step) => ({ ...step }));
329
+ this.branch = branch ?? treeState.activeBranch.id;
330
+ }
331
+ static validate(tree) {
332
+ wizardRules.forEach((rule) => rule.verify(tree));
333
+ return tree;
334
+ }
335
+ /** Activate completed steps + the next step after the last completed one. */
336
+ activateCategory(categoryId) {
337
+ const shown = this.getCategoryStepStates(categoryId).filter((s) => s.isShow);
338
+ for (let i = 0; i < shown.length; i++) {
339
+ const step = shown[i];
340
+ const prev = shown[i - 1];
341
+ step.isActive = step.isCompleted || (prev?.isCompleted ?? true);
342
+ }
343
+ return this;
344
+ }
345
+ /** Activate first shown step of a category; deactivate the rest. */
346
+ activateFirstStep(categoryId) {
347
+ const shown = this.getCategoryStepStates(categoryId).filter((step) => step.isShow);
348
+ if (shown.length === 0) return this;
349
+ shown[0].isActive = true;
350
+ for (let i = 1; i < shown.length; i++) {
351
+ shown[i].isActive = false;
352
+ }
353
+ return this;
354
+ }
355
+ // ================
356
+ // Step actions
357
+ // ================
358
+ activateStep(step) {
359
+ this.getStepState(step).isActive = true;
360
+ return this;
361
+ }
362
+ build() {
363
+ const tree = buildWizardTreeState(this.state, this.branch);
364
+ _WizardTreeStateBuilder.validate(tree);
365
+ return Object.freeze(tree);
366
+ }
367
+ /** Build a terminal complete tree (skips rule validation since the tree is sealed). */
368
+ buildComplete() {
369
+ const tree = buildWizardTreeState(this.state, this.branch);
370
+ return Object.freeze(new WizardCompleteTreeState(tree));
371
+ }
372
+ /** Build a terminal exit tree (skips rule validation since the tree is sealed). */
373
+ buildExit() {
374
+ const tree = buildWizardTreeState(this.state, this.branch);
375
+ return Object.freeze(new WizardExitTreeState(tree));
376
+ }
377
+ completeCategory(categoryId) {
378
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isCompleted = true);
379
+ return this;
380
+ }
381
+ // ================
382
+ // Category actions
383
+ // ================
384
+ completeStep(step) {
385
+ this.getStepState(step).isCompleted = true;
386
+ return this;
387
+ }
388
+ deactivateCategory(categoryId) {
389
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isActive = false);
390
+ return this;
391
+ }
392
+ deactivateStep(step) {
393
+ this.getStepState(step).isActive = false;
394
+ return this;
395
+ }
396
+ getCategoryStepStates(categoryId) {
397
+ return this.state.filter((step) => step.categoryId === categoryId);
398
+ }
399
+ /** Reset every step in the tree to `wizardDefaultState`. */
400
+ getResetTree() {
401
+ this.state.forEach((step) => Object.assign(step, wizardDefaultState));
402
+ const firstStep = this.state[0];
403
+ if (firstStep) {
404
+ firstStep.isActive = true;
405
+ }
406
+ return Object.freeze(buildWizardTreeState(this.state, this.branch));
407
+ }
408
+ getStepState(step) {
409
+ const found = this.state.find((s) => s.id === step);
410
+ if (!found) {
411
+ throw new Error(`[WizardTreeStateBuilder] Unknown step "${step}"`);
412
+ }
413
+ return found;
414
+ }
415
+ hideCategory(categoryId) {
416
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isShow = false);
417
+ return this;
418
+ }
419
+ hideStep(step) {
420
+ this.getStepState(step).isShow = false;
421
+ return this;
422
+ }
423
+ /** Reset category steps from `from` index onward; deactivate all but first. */
424
+ resetCategory(categoryId, from = 0) {
425
+ const slice = this.getCategoryStepStates(categoryId).slice(from);
426
+ slice.forEach((step, index) => {
427
+ step.isActive = step.isActive && index === 0;
428
+ step.isCompleted = false;
429
+ });
430
+ return this;
431
+ }
432
+ showCategory(categoryId) {
433
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isShow = true);
434
+ return this;
435
+ }
436
+ // ================
437
+ // Build / reset
438
+ // ================
439
+ showStep(step) {
440
+ this.getStepState(step).isShow = true;
441
+ return this;
442
+ }
443
+ skipCategory(categoryId) {
444
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isSkipped = true);
445
+ return this;
446
+ }
447
+ skipCategoryUncompleted(categoryId) {
448
+ this.getCategoryStepStates(categoryId).filter((step) => !step.isCompleted).forEach((step) => step.isSkipped = true);
449
+ return this;
450
+ }
451
+ uncompleteStep(step) {
452
+ this.getStepState(step).isCompleted = false;
453
+ return this;
454
+ }
455
+ unskipCategory(categoryId) {
456
+ this.getCategoryStepStates(categoryId).forEach((step) => step.isSkipped = false);
457
+ return this;
458
+ }
459
+ };
460
+
461
+ // src/core/compose-wizard-providers.ts
462
+ function composeWizardProviders(...mods) {
463
+ const props = {
464
+ config: {},
465
+ initializers: [],
466
+ listeners: [],
467
+ strategies: {}
468
+ };
469
+ for (const mod of mods) {
470
+ mod(props);
471
+ }
472
+ return props;
473
+ }
474
+ var withConfig = (options) => (props) => {
475
+ props.config = { ...props.config, ...options };
476
+ };
477
+ var withInitializer = (initializer) => (props) => {
478
+ props.initializers.push(initializer);
479
+ };
480
+ var withListener = (listener) => (props) => {
481
+ props.listeners.push(listener);
482
+ };
483
+ var withScrollStrategy = (strategy) => (props) => {
484
+ props.strategies.scroll = strategy;
485
+ };
486
+ var withScrollContainer = (container) => (props) => {
487
+ props.scrollContainer = container;
488
+ };
489
+ var withProgressStrategy = (strategy) => (props) => {
490
+ props.strategies.progress = strategy;
491
+ };
492
+ var withVisibilityStrategy = (strategy) => (props) => {
493
+ props.strategies.visibility = strategy;
494
+ };
495
+
496
+ // src/core/events/wizard-events.ts
497
+ var WizardEvent = class {
498
+ /** @param navigation - Specific navigation data associated with event */
499
+ constructor(navigation) {
500
+ this.navigation = navigation;
501
+ }
502
+ navigation;
503
+ };
504
+ var WizardCompleteEvent = class extends WizardEvent {
505
+ constructor(navigation) {
506
+ super(navigation);
507
+ this.navigation = navigation;
508
+ }
509
+ navigation;
510
+ type = "complete" /* Complete */;
511
+ };
512
+ var WizardExitEvent = class extends WizardEvent {
513
+ type = "exit" /* Exit */;
514
+ };
515
+ var WizardNavigationCancelledEvent = class extends WizardEvent {
516
+ constructor(navigation, reason) {
517
+ super(navigation);
518
+ this.navigation = navigation;
519
+ this.reason = reason;
520
+ }
521
+ navigation;
522
+ reason;
523
+ type = "navigation_cancelled" /* NavigationCancelled */;
524
+ };
525
+ var WizardNavigationEndEvent = class extends WizardEvent {
526
+ constructor(navigation) {
527
+ super(navigation);
528
+ this.navigation = navigation;
529
+ }
530
+ navigation;
531
+ type = "navigation_end" /* NavigationEnd */;
532
+ };
533
+ var WizardNavigationErrorEvent = class extends WizardEvent {
534
+ constructor(navigation) {
535
+ super(navigation);
536
+ this.navigation = navigation;
537
+ }
538
+ navigation;
539
+ type = "navigation_error" /* NavigationError */;
540
+ };
541
+ var WizardNavigationIgnoredEvent = class extends WizardEvent {
542
+ constructor(navigation, reason) {
543
+ super(navigation);
544
+ this.navigation = navigation;
545
+ this.reason = reason;
546
+ }
547
+ navigation;
548
+ reason;
549
+ type = "navigation_ignored" /* NavigationIgnored */;
550
+ };
551
+ var WizardNavigationStartEvent = class extends WizardEvent {
552
+ constructor(navigation) {
553
+ super(navigation);
554
+ this.navigation = navigation;
555
+ }
556
+ navigation;
557
+ type = "navigation_start" /* NavigationStart */;
558
+ };
559
+ var WizardResolveEndEvent = class extends WizardEvent {
560
+ constructor(navigation) {
561
+ super(navigation);
562
+ this.navigation = navigation;
563
+ }
564
+ navigation;
565
+ type = "resolve_end" /* ResolveEnd */;
566
+ };
567
+ var WizardResolveStartEvent = class extends WizardEvent {
568
+ constructor(navigation) {
569
+ super(navigation);
570
+ this.navigation = navigation;
571
+ }
572
+ navigation;
573
+ type = "resolve_start" /* ResolveStart */;
574
+ };
575
+ var WizardScrollEndEvent = class extends WizardEvent {
576
+ constructor(navigation) {
577
+ super(navigation);
578
+ this.navigation = navigation;
579
+ }
580
+ navigation;
581
+ type = "scroll_end" /* ScrollEnd */;
582
+ };
583
+ var WizardScrollStartEvent = class extends WizardEvent {
584
+ constructor(navigation) {
585
+ super(navigation);
586
+ this.navigation = navigation;
587
+ }
588
+ navigation;
589
+ type = "scroll_start" /* ScrollStart */;
590
+ };
591
+ var WizardStepHideEvent = class extends WizardEvent {
592
+ constructor(navigation) {
593
+ super(navigation);
594
+ this.navigation = navigation;
595
+ }
596
+ navigation;
597
+ type = "step_hide" /* StepHide */;
598
+ };
599
+ var WizardStepShowEvent = class extends WizardEvent {
600
+ constructor(navigation) {
601
+ super(navigation);
602
+ this.navigation = navigation;
603
+ }
604
+ navigation;
605
+ type = "step_show" /* StepShow */;
606
+ };
607
+
608
+ // src/core/initializers/wizard-initializer.ts
609
+ var WizardInitializer = class {
610
+ };
611
+
612
+ // src/core/initializers/wizard-default.initializer.ts
613
+ var WizardDefaultInitializer = class extends WizardInitializer {
614
+ getState(tree) {
615
+ const builder = new WizardTreeStateBuilder(tree);
616
+ if (tree.activeSteps.length > 0) {
617
+ return builder.build();
618
+ }
619
+ const firstUnpassed = tree.activeBranch.shownCategories.find((category) => !category.isPassed) ?? tree.activeCategory;
620
+ return builder.activateCategory(firstUnpassed.id).build();
621
+ }
622
+ };
623
+
624
+ // src/core/listeners/wizard-listener.ts
625
+ var WizardListener = class {
626
+ onDestroy(_tree) {
627
+ return;
628
+ }
629
+ onEvent(_event) {
630
+ return;
631
+ }
632
+ onInit(_tree) {
633
+ return;
634
+ }
635
+ onTreeChange(_tree) {
636
+ return;
637
+ }
638
+ };
639
+
640
+ // src/core/navigation/initializer-service.ts
641
+ var WizardInitializerService = class {
642
+ constructor(wizardLog, wizardInitializers) {
643
+ this.wizardLog = wizardLog;
644
+ this.wizardInitializers = wizardInitializers;
645
+ }
646
+ wizardLog;
647
+ wizardInitializers;
648
+ async getInitialState(state, activeBranch) {
649
+ const htmlTree = buildWizardTreeState(state, activeBranch);
650
+ this.wizardLog.trace("HTML tree", htmlTree);
651
+ try {
652
+ this.validateHtmlTree(htmlTree);
653
+ this.validateActiveBranch(htmlTree, activeBranch);
654
+ let tree = htmlTree;
655
+ for (const initializer of this.wizardInitializers) {
656
+ tree = await initializer.getState(tree, htmlTree);
657
+ this.wizardLog.trace("Initializer", initializer, tree);
658
+ }
659
+ return tree;
660
+ } catch (e) {
661
+ this.wizardLog.error(e);
662
+ return htmlTree;
663
+ }
664
+ }
665
+ validateActiveBranch(htmlTree, activeBranch) {
666
+ if (!activeBranch) return;
667
+ if (!htmlTree.branchMap[activeBranch]) {
668
+ const branchIds = htmlTree.branches.map((branch) => branch.id).join(",");
669
+ throw new WizardError(
670
+ `[WizardInitializerService02]: There is no branch with id "${activeBranch}". List of available branches: ${branchIds}.`
671
+ );
672
+ }
673
+ }
674
+ validateHtmlTree(htmlTree) {
675
+ const seen = /* @__PURE__ */ new Set();
676
+ const duplicates = [];
677
+ for (const s of htmlTree.state) {
678
+ if (seen.has(s.id)) duplicates.push(s.id);
679
+ else seen.add(s.id);
680
+ }
681
+ if (duplicates.length > 0) {
682
+ throw new WizardInitializationError(
683
+ `[WizardInitializerService01]: There are steps with the same ID: ${duplicates.join(",")}.`
684
+ );
685
+ }
686
+ }
687
+ };
688
+
689
+ // src/core/navigation/typed-emitter.ts
690
+ var TypedEmitter = class {
691
+ map = /* @__PURE__ */ new Map();
692
+ dispose() {
693
+ this.map.clear();
694
+ }
695
+ emit(event) {
696
+ const specific = this.map.get(event.type);
697
+ if (specific) {
698
+ for (const handler of [...specific]) handler(event);
699
+ }
700
+ const wildcard = this.map.get("*");
701
+ if (wildcard) {
702
+ for (const handler of [...wildcard]) handler(event);
703
+ }
704
+ }
705
+ on(type, handler) {
706
+ let set = this.map.get(type);
707
+ if (!set) {
708
+ set = /* @__PURE__ */ new Set();
709
+ this.map.set(type, set);
710
+ }
711
+ set.add(handler);
712
+ return () => {
713
+ set.delete(handler);
714
+ };
715
+ }
716
+ };
717
+
718
+ // src/core/navigation/wizard-navigation.ts
719
+ var WizardNavigation = class _WizardNavigation {
720
+ constructor(from, to) {
721
+ this.from = from;
722
+ this.to = to;
723
+ const toTree = projectToTree(to);
724
+ this.id = `${from.id}=>${toTree.id}`;
725
+ this.direction = _WizardNavigation.getDirection(from, toTree);
726
+ this.isSkipping = _WizardNavigation.isSkipping(from, toTree, this.direction);
727
+ this.isCategoryChanging = _WizardNavigation.isCategoryChanging(from, toTree);
728
+ this.isScrollChanging = _WizardNavigation.isScrollChanging(from, toTree);
729
+ this.isExit = to instanceof WizardExitTreeState;
730
+ this.isComplete = to instanceof WizardCompleteTreeState;
731
+ }
732
+ from;
733
+ to;
734
+ direction;
735
+ id;
736
+ isCategoryChanging;
737
+ isComplete;
738
+ isExit;
739
+ isScrollChanging;
740
+ isSkipping;
741
+ static getDirection(from, to) {
742
+ if (_WizardNavigation.isCategoryChanging(from, to)) {
743
+ return from.activeCategory.htmlIndex > to.activeCategory.htmlIndex ? "back" : "next";
744
+ }
745
+ if (from.activeCategory.activeStepIndex === to.activeCategory.activeStepIndex) {
746
+ return to.activeCategory.id === to.activeBranch.firstCategory.id ? "back" : "next";
747
+ }
748
+ return from.activeCategory.activeStepIndex > to.activeCategory.activeStepIndex ? "back" : "next";
749
+ }
750
+ static isCategoryChanging(from, to) {
751
+ return from.activeCategory.id !== to.activeCategory.id;
752
+ }
753
+ static isScrollChanging(from, to) {
754
+ const isSamePosition = from.activeCategory.activeStepIndex === to.activeCategory.activeStepIndex;
755
+ const isSameStep = from.activeCategory.lastActiveStep.id === to.activeCategory.lastActiveStep.id;
756
+ return !isSameStep || !isSamePosition;
757
+ }
758
+ /**
759
+ * Navigation counted as skipped when Category "A" was "active" and not "skipped" before transition
760
+ * and became "inactive" and "skipped" after transition.
761
+ */
762
+ static isSkipping(from, to, direction = _WizardNavigation.getDirection(from, to)) {
763
+ if (direction === "back") {
764
+ return false;
765
+ }
766
+ const isPrevCategoryAlreadySkipped = from.activeCategory.isSkipped;
767
+ const isPrevCategorySkipped = to.categoryMap[from.activeCategory.id].isSkipped;
768
+ return !isPrevCategoryAlreadySkipped && isPrevCategorySkipped;
769
+ }
770
+ };
771
+
772
+ // src/core/navigation/navigation-service.ts
773
+ var WizardNavigationService = class {
774
+ constructor(wizardStore, wizardConfig, wizardStateService, wizardScrollStrategy) {
775
+ this.wizardStore = wizardStore;
776
+ this.wizardConfig = wizardConfig;
777
+ this.wizardStateService = wizardStateService;
778
+ this.wizardScrollStrategy = wizardScrollStrategy;
779
+ }
780
+ wizardStore;
781
+ wizardConfig;
782
+ wizardStateService;
783
+ wizardScrollStrategy;
784
+ get isNavigating() {
785
+ return !!this.inFlight;
786
+ }
787
+ disposed = false;
788
+ emitter = new TypedEmitter();
789
+ inFlight = null;
790
+ prevSuccessId = null;
791
+ async completeWizard() {
792
+ const tree = this.wizardStore.getTreeSnapshot();
793
+ const to = new WizardCompleteTreeState(tree);
794
+ return this.navigate(to);
795
+ }
796
+ dispose() {
797
+ this.disposed = true;
798
+ if (this.inFlight) {
799
+ this.inFlight.abort.abort();
800
+ this.inFlight = null;
801
+ }
802
+ this.emitter.dispose();
803
+ }
804
+ async exitWizard() {
805
+ try {
806
+ const tree = this.wizardStore.getTreeSnapshot();
807
+ const to = new WizardExitTreeState(tree);
808
+ return this.navigate(to);
809
+ } catch {
810
+ this.emitter.emit(new WizardExitEvent());
811
+ return true;
812
+ }
813
+ }
814
+ async hideCategory(categoryId) {
815
+ const to = this.wizardStateService.hideCategory(categoryId);
816
+ return this.hide(this.projectTree(to));
817
+ }
818
+ async hideStep(step) {
819
+ const tree = this.wizardStore.getTreeSnapshot();
820
+ const to = toArray(step).reduce((acc, id) => {
821
+ const accTree = this.projectTree(acc);
822
+ return this.wizardStateService.hideStep(id, accTree);
823
+ }, tree);
824
+ return this.hide(this.projectTree(to));
825
+ }
826
+ async navigate(to) {
827
+ if (this.disposed) return false;
828
+ const fromTree = this.wizardStore.getTreeSnapshot();
829
+ const navigation = new WizardNavigation(fromTree, to);
830
+ if (navigation.id === this.prevSuccessId && !navigation.isExit && !navigation.isComplete) {
831
+ this.emitter.emit(new WizardNavigationIgnoredEvent(navigation, "same_navigation"));
832
+ return false;
833
+ }
834
+ if (this.inFlight) {
835
+ const prior = this.inFlight;
836
+ prior.abort.abort();
837
+ this.inFlight = null;
838
+ this.emitter.emit(new WizardNavigationCancelledEvent(prior.navigation, "new_navigation"));
839
+ }
840
+ const abort = new AbortController();
841
+ this.inFlight = { abort, navigation };
842
+ try {
843
+ this.emitter.emit(new WizardNavigationStartEvent(navigation));
844
+ this.emitter.emit(new WizardResolveStartEvent(navigation));
845
+ try {
846
+ await this.runResolverChain(navigation, abort.signal);
847
+ } catch (e) {
848
+ if (this.isAbortReason(e, abort.signal)) {
849
+ return false;
850
+ }
851
+ if (e instanceof WizardResolverError) {
852
+ this.emitter.emit(new WizardNavigationCancelledEvent(navigation, "resolver"));
853
+ return false;
854
+ }
855
+ this.emitter.emit(new WizardNavigationErrorEvent(navigation));
856
+ console.error(e);
857
+ throw e;
858
+ }
859
+ if (abort.signal.aborted) return false;
860
+ this.emitter.emit(new WizardResolveEndEvent(navigation));
861
+ this.commitTree(navigation, to);
862
+ this.emitVisibilityFlips(navigation, fromTree, to);
863
+ this.emitter.emit(new WizardNavigationEndEvent(navigation));
864
+ if (navigation.isExit) this.emitter.emit(new WizardExitEvent(navigation));
865
+ if (navigation.isComplete) this.emitter.emit(new WizardCompleteEvent(navigation));
866
+ if (!navigation.isExit && !navigation.isComplete && navigation.isScrollChanging) {
867
+ this.emitter.emit(new WizardScrollStartEvent(navigation));
868
+ await this.wizardScrollStrategy.onNavigate(navigation);
869
+ this.emitter.emit(new WizardScrollEndEvent(navigation));
870
+ }
871
+ this.prevSuccessId = navigation.id;
872
+ return true;
873
+ } finally {
874
+ if (this.inFlight && this.inFlight.navigation === navigation) {
875
+ this.inFlight = null;
876
+ }
877
+ }
878
+ }
879
+ on(type, handler) {
880
+ return this.emitter.on(type, handler);
881
+ }
882
+ async showCategory(categoryId) {
883
+ const to = this.wizardStateService.showCategory(categoryId);
884
+ return this.show(to);
885
+ }
886
+ async showStep(step) {
887
+ const tree = this.wizardStore.getTreeSnapshot();
888
+ const to = toArray(step).reduce(
889
+ (acc, id) => this.wizardStateService.showStep(id, acc),
890
+ tree
891
+ );
892
+ return this.show(to);
893
+ }
894
+ // --- internals ---
895
+ commitTree(navigation, to) {
896
+ if (navigation.isExit || navigation.isComplete) {
897
+ const tree = this.projectTree(to);
898
+ this.wizardStore.setState(tree);
899
+ return;
900
+ }
901
+ this.wizardStore.setState(to);
902
+ }
903
+ emitVisibilityFlips(navigation, from, to) {
904
+ const toTree = this.projectTree(to);
905
+ for (const stepId of Object.keys(from.stepMap)) {
906
+ const before = from.stepMap[stepId];
907
+ const after = toTree.stepMap[stepId];
908
+ if (!before || !after) continue;
909
+ if (before.isShow === after.isShow) continue;
910
+ if (after.isShow) {
911
+ this.emitter.emit(new WizardStepShowEvent(navigation));
912
+ } else {
913
+ this.emitter.emit(new WizardStepHideEvent(navigation));
914
+ }
915
+ }
916
+ }
917
+ async hide(to) {
918
+ const fromTree = this.wizardStore.getTreeSnapshot();
919
+ const navigation = new WizardNavigation(fromTree, to);
920
+ this.wizardStore.setState(to);
921
+ this.emitter.emit(new WizardStepHideEvent(navigation));
922
+ if (navigation.isScrollChanging) {
923
+ await this.wizardScrollStrategy.onToggle(navigation);
924
+ }
925
+ return true;
926
+ }
927
+ async invokeResolver(resolver, navigation, signal) {
928
+ if (!resolver) return;
929
+ if (signal.aborted) throw new DOMException("aborted", "AbortError");
930
+ const abortPromise = new Promise((_, reject) => {
931
+ const onAbort = () => reject(new DOMException("aborted", "AbortError"));
932
+ if (signal.aborted) {
933
+ onAbort();
934
+ } else {
935
+ signal.addEventListener("abort", onAbort, { once: true });
936
+ }
937
+ });
938
+ const result = await Promise.race([Promise.resolve(resolver(navigation)), abortPromise]);
939
+ if (signal.aborted) throw new DOMException("aborted", "AbortError");
940
+ if (!result) {
941
+ throw new WizardResolverError(`Resolver rejected navigation ${navigation.id}`);
942
+ }
943
+ }
944
+ isAbortReason(error, signal) {
945
+ if (!signal.aborted) return false;
946
+ if (error instanceof DOMException && error.name === "AbortError") return true;
947
+ return false;
948
+ }
949
+ projectTree(to) {
950
+ if (to instanceof WizardExitTreeState || to instanceof WizardCompleteTreeState) {
951
+ return to.originTree;
952
+ }
953
+ return to;
954
+ }
955
+ async runResolverChain(navigation, signal) {
956
+ const config = this.wizardConfig.getOptions();
957
+ if (navigation.direction === "next" && navigation.isCategoryChanging && !navigation.isSkipping) {
958
+ await this.invokeResolver(config.resolveCategoryComplete, navigation, signal);
959
+ }
960
+ if (navigation.isCategoryChanging) {
961
+ await this.invokeResolver(config.resolveCategoryChange, navigation, signal);
962
+ }
963
+ if (navigation.isCategoryChanging) {
964
+ await this.invokeResolver(config.resolveCategoryEnter, navigation, signal);
965
+ }
966
+ await this.invokeResolver(config.resolveStepChange, navigation, signal);
967
+ }
968
+ async show(to) {
969
+ const fromTree = this.wizardStore.getTreeSnapshot();
970
+ const toTree = this.projectTree(to);
971
+ const navigation = new WizardNavigation(fromTree, toTree);
972
+ this.wizardStore.setState(toTree);
973
+ this.emitter.emit(new WizardStepShowEvent(navigation));
974
+ if (navigation.isScrollChanging) {
975
+ await this.wizardScrollStrategy.onToggle(navigation);
976
+ }
977
+ return true;
978
+ }
979
+ };
980
+
981
+ // src/core/navigation/state-service.ts
982
+ var WizardStateService = class {
983
+ constructor(wizardStore, wizardConfig) {
984
+ this.wizardStore = wizardStore;
985
+ this.wizardConfig = wizardConfig;
986
+ }
987
+ wizardStore;
988
+ wizardConfig;
989
+ completeCategory(category, tree = this.currentTree()) {
990
+ const { activeBranch } = tree;
991
+ const { activeCategory, nextCategory } = activeBranch;
992
+ const builder = new WizardTreeStateBuilder(tree);
993
+ builder.completeCategory(category);
994
+ if (activeCategory.id === category && nextCategory) {
995
+ return builder.deactivateCategory(category).activateCategory(nextCategory.id).build();
996
+ }
997
+ if (activeCategory.id === category && !nextCategory) {
998
+ return builder.buildComplete();
999
+ }
1000
+ return builder.build();
1001
+ }
1002
+ goToCategory(categoryId, tree = this.currentTree()) {
1003
+ const { activeBranch, activeCategory, branchMap, categoryMap } = tree;
1004
+ const builder = new WizardTreeStateBuilder(tree);
1005
+ const toCategory = categoryMap[categoryId];
1006
+ const toCategoryFirstBranch = branchMap[toCategory.branches[0]];
1007
+ const toCategoryBranch = toCategory.branches.includes(activeBranch.id) ? activeBranch : toCategoryFirstBranch;
1008
+ const toCategoryIndex = toCategoryBranch.getShownCategoryIndex(toCategory);
1009
+ const activeCategoryIndex = toCategoryBranch.getShownCategoryIndex(activeCategory);
1010
+ if (activeBranch !== toCategoryBranch && toCategoryIndex > activeCategoryIndex) {
1011
+ throw new WizardError(
1012
+ `[State02]: Trying to switch to category "${categoryId}" in different branch "${toCategoryBranch.id}" from current active branch "${activeBranch.id}", but new category has uncompleted prev category in new branch.`
1013
+ );
1014
+ }
1015
+ if (toCategoryIndex > activeCategoryIndex) {
1016
+ activeBranch.getShownCategories(activeCategoryIndex, toCategoryIndex - 1).forEach((mid) => builder.skipCategoryUncompleted(mid.id));
1017
+ }
1018
+ if (this.wizardConfig.getOptions().isBackResetCompleted && toCategoryIndex < activeCategoryIndex) {
1019
+ activeBranch.getShownCategories(toCategoryIndex + 1, activeCategoryIndex).forEach((mid) => builder.resetCategory(mid.id));
1020
+ }
1021
+ builder.deactivateCategory(activeCategory.id);
1022
+ if (this.wizardConfig.getOptions().isShowFirstStepOnCategoryChange) {
1023
+ return builder.activateFirstStep(categoryId).build();
1024
+ }
1025
+ return builder.activateCategory(categoryId).build();
1026
+ }
1027
+ hideCategory(categoryId, tree = this.currentTree()) {
1028
+ const { activeBranch, activeCategory } = tree;
1029
+ const projected = activeCategory.id === categoryId ? this.projectShownCategory(categoryId, tree) : tree;
1030
+ const builder = new WizardTreeStateBuilder(projected, activeBranch.id);
1031
+ return builder.hideCategory(categoryId).build();
1032
+ }
1033
+ hideStep(step, tree = this.currentTree()) {
1034
+ const { activeBranch, lastActiveStep } = tree;
1035
+ const projected = lastActiveStep.id === step ? this.projectShownStep(step, tree) : tree;
1036
+ const builder = new WizardTreeStateBuilder(projected, activeBranch.id);
1037
+ return builder.hideStep(step).deactivateStep(step).build();
1038
+ }
1039
+ next(branch, tree = this.currentTree()) {
1040
+ const { activeBranch, branchMap } = tree;
1041
+ const nextBranch = branch ? branchMap[branch] : activeBranch;
1042
+ const builder = new WizardTreeStateBuilder(tree, branch ?? activeBranch.id);
1043
+ const activeCategory = nextBranch.activeCategory;
1044
+ const activeStep = activeCategory.lastActiveStep;
1045
+ const nextCategory = nextBranch.nextCategory;
1046
+ const nextStep = activeCategory.nextStep;
1047
+ if (activeBranch.activeCategory !== nextBranch.activeCategory) {
1048
+ throw new WizardError(
1049
+ `[State01]: In order to move to the "${branch}" branch, you need to have common category between 2 branches`
1050
+ );
1051
+ }
1052
+ builder.unskipCategory(activeCategory.id).completeStep(activeStep.id);
1053
+ if (nextStep) {
1054
+ return builder.activateStep(nextStep.id).build();
1055
+ }
1056
+ if (nextCategory) {
1057
+ builder.deactivateCategory(activeCategory.id);
1058
+ if (this.wizardConfig.getOptions().isShowFirstStepOnCategoryChange) {
1059
+ return builder.activateFirstStep(nextCategory.id).build();
1060
+ }
1061
+ return builder.activateCategory(nextCategory.id).build();
1062
+ }
1063
+ return builder.buildComplete();
1064
+ }
1065
+ nextCategory(branch, tree = this.currentTree()) {
1066
+ const { activeBranch, branchMap } = tree;
1067
+ const nextBranch = branch ? branchMap[branch] : activeBranch;
1068
+ const builder = new WizardTreeStateBuilder(tree, branch ?? activeBranch.id);
1069
+ const activeCategory = nextBranch.activeCategory;
1070
+ const activeStep = activeCategory.lastActiveStep;
1071
+ const nextCategory = nextBranch.nextCategory;
1072
+ if (activeBranch.activeCategory !== nextBranch.activeCategory) {
1073
+ throw new WizardError(
1074
+ `[State01]: In order to move to the "${branch}" branch, you need to have common category between 2 branches`
1075
+ );
1076
+ }
1077
+ builder.deactivateCategory(activeCategory.id).completeStep(activeStep.id);
1078
+ if (nextCategory) {
1079
+ builder.skipCategoryUncompleted(activeCategory.id);
1080
+ if (this.wizardConfig.getOptions().isShowFirstStepOnCategoryChange) {
1081
+ return builder.activateFirstStep(nextCategory.id).build();
1082
+ }
1083
+ return builder.activateCategory(nextCategory.id).build();
1084
+ }
1085
+ return builder.buildComplete();
1086
+ }
1087
+ prev(tree = this.currentTree()) {
1088
+ const { activeBranch } = tree;
1089
+ const builder = new WizardTreeStateBuilder(tree);
1090
+ const activeCategory = activeBranch.activeCategory;
1091
+ const activeStep = activeCategory.lastActiveStep;
1092
+ const prevCategory = activeBranch.prevCategory;
1093
+ const prevStep = activeCategory.prevStep;
1094
+ if (this.wizardConfig.getOptions().isBackResetCompleted) {
1095
+ builder.uncompleteStep(activeStep.id);
1096
+ }
1097
+ if (prevStep) {
1098
+ return builder.deactivateStep(activeStep.id).build();
1099
+ }
1100
+ if (prevCategory) {
1101
+ return builder.deactivateCategory(activeCategory.id).activateCategory(prevCategory.id).build();
1102
+ }
1103
+ return builder.buildExit();
1104
+ }
1105
+ prevCategory(tree = this.currentTree()) {
1106
+ const { activeBranch } = tree;
1107
+ const builder = new WizardTreeStateBuilder(tree);
1108
+ const activeCategory = activeBranch.activeCategory;
1109
+ const prevCategory = activeBranch.prevCategory;
1110
+ if (prevCategory && this.wizardConfig.getOptions().isBackResetCompleted) {
1111
+ builder.resetCategory(activeCategory.id);
1112
+ }
1113
+ if (prevCategory) {
1114
+ return builder.deactivateCategory(activeCategory.id).activateCategory(prevCategory.id).build();
1115
+ }
1116
+ return builder.buildExit();
1117
+ }
1118
+ resetCategory(category, from = 0, tree = this.currentTree()) {
1119
+ const { activeCategory } = tree;
1120
+ const builder = new WizardTreeStateBuilder(tree);
1121
+ builder.resetCategory(category, from);
1122
+ if (activeCategory.id === category) {
1123
+ return builder.activateStep(activeCategory.firstStep.id).build();
1124
+ }
1125
+ return builder.build();
1126
+ }
1127
+ resetTree(tree = this.currentTree()) {
1128
+ const reset = new WizardTreeStateBuilder(tree).getResetTree();
1129
+ return new WizardTreeStateBuilder(reset, tree.activeBranch.id).activateStep(reset.activeCategory.firstStep.id).build();
1130
+ }
1131
+ showCategory(categoryId, tree = this.currentTree()) {
1132
+ const { activeBranch } = tree;
1133
+ const builder = new WizardTreeStateBuilder(tree);
1134
+ const activeCategoryIndex = activeBranch.categories.indexOf(activeBranch.activeCategory);
1135
+ const categoryIndex = activeBranch.categories.findIndex((category) => category.id === categoryId);
1136
+ const isBeforeActiveCategory = activeCategoryIndex > categoryIndex;
1137
+ builder.showCategory(categoryId);
1138
+ if (isBeforeActiveCategory) builder.skipCategory(categoryId);
1139
+ return builder.build();
1140
+ }
1141
+ showStep(step, tree = this.currentTree()) {
1142
+ const { activeCategory, lastActiveStep, stepMap } = tree;
1143
+ const builder = new WizardTreeStateBuilder(tree);
1144
+ const stepState = stepMap[step];
1145
+ const isInActiveCategory = activeCategory.steps.some((s) => s.id === step);
1146
+ const isBeforeLastActiveStep = lastActiveStep.htmlIndex > stepState.htmlIndex;
1147
+ builder.showStep(step);
1148
+ if (isBeforeLastActiveStep) builder.completeStep(step);
1149
+ if (isInActiveCategory && isBeforeLastActiveStep) builder.activateStep(step);
1150
+ return builder.build();
1151
+ }
1152
+ skipCategory(category, tree = this.currentTree()) {
1153
+ const { activeBranch } = tree;
1154
+ const { activeCategory, nextCategory } = activeBranch;
1155
+ const builder = new WizardTreeStateBuilder(tree);
1156
+ builder.skipCategory(category);
1157
+ if (activeCategory.id === category && nextCategory) {
1158
+ return builder.deactivateCategory(category).activateCategory(nextCategory.id).build();
1159
+ }
1160
+ if (activeCategory.id === category && !nextCategory) {
1161
+ return builder.buildComplete();
1162
+ }
1163
+ return builder.build();
1164
+ }
1165
+ currentTree() {
1166
+ return this.wizardStore.getTreeSnapshot();
1167
+ }
1168
+ projectShownCategory(categoryId, tree) {
1169
+ const { activeBranch } = tree;
1170
+ const result = categoryId === activeBranch.firstCategory.id ? this.nextCategory(null, tree) : this.prevCategory(tree);
1171
+ return result;
1172
+ }
1173
+ projectShownStep(step, tree) {
1174
+ const { activeBranch, activeCategory, stepMap } = tree;
1175
+ const stepState = stepMap[step];
1176
+ const isFirstCategory = stepState.categoryId === activeBranch.firstCategory.id;
1177
+ const isFirstStep = step === activeCategory.firstStep.id;
1178
+ const result = isFirstCategory && isFirstStep ? this.next(null, tree) : this.prev(tree);
1179
+ return result;
1180
+ }
1181
+ };
1182
+
1183
+ // src/core/strategies/progress/wizard-progress.strategy.ts
1184
+ var WizardProgressStrategy = class {
1185
+ };
1186
+
1187
+ // src/core/strategies/progress/wizard-active-progress.strategy.ts
1188
+ var WizardActiveProgressStrategy = class extends WizardProgressStrategy {
1189
+ getProgress(tree) {
1190
+ const { activeCategory, categoryMap } = tree;
1191
+ const progress = {};
1192
+ for (const category of Object.keys(categoryMap)) {
1193
+ const categoryState = categoryMap[category];
1194
+ if (categoryState.htmlIndex !== activeCategory.htmlIndex) {
1195
+ progress[category] = categoryState.htmlIndex < activeCategory.htmlIndex ? 100 : 0;
1196
+ continue;
1197
+ }
1198
+ progress[category] = categoryState.shownSteps.length > 0 ? (categoryState.activeSteps.length - 1) * 100 / categoryState.shownSteps.length : 0;
1199
+ }
1200
+ return progress;
1201
+ }
1202
+ };
1203
+
1204
+ // src/core/strategies/scroll/wizard-scroll.strategy.ts
1205
+ var WizardScrollStrategy = class {
1206
+ // Thin re-export so subclasses keep the existing call site.
1207
+ static projectToTree = projectToTree;
1208
+ container;
1209
+ /** Called when toggling step/category visibility (instant scroll by default). */
1210
+ onToggle(navigation) {
1211
+ const tree = projectToTree(navigation.to);
1212
+ if (!tree) return Promise.resolve(false);
1213
+ const isFirstStepOfCategory = tree.lastActiveStep?.id === tree.activeCategory.firstStep?.id;
1214
+ return scrollToStep(isFirstStepOfCategory ? null : tree.lastActiveStep?.id, "auto", this.container);
1215
+ }
1216
+ /**
1217
+ * Imperative scroll-to-top of the configured container (or window). Useful
1218
+ * for surfacing top-of-page state — e.g. an error banner — after a failed
1219
+ * submit, without re-running the navigation pipeline.
1220
+ */
1221
+ scrollToTop(behavior = "smooth") {
1222
+ return scrollToStep(null, behavior, this.container);
1223
+ }
1224
+ setContainer(container) {
1225
+ this.container = container;
1226
+ }
1227
+ };
1228
+ function scrollToStep(stepId, behavior, container) {
1229
+ if (typeof document === "undefined") return Promise.resolve(false);
1230
+ const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
1231
+ const effectiveBehavior = reducedMotion ? "auto" : behavior;
1232
+ if (!stepId) {
1233
+ const containerEl = resolveScrollContainer(container);
1234
+ if (containerEl) {
1235
+ containerEl.scrollTo({ behavior: effectiveBehavior, top: 0 });
1236
+ } else if (typeof window !== "undefined") {
1237
+ window.scrollTo({ behavior: effectiveBehavior, top: 0 });
1238
+ }
1239
+ return Promise.resolve(true);
1240
+ }
1241
+ const element = document.getElementById(stepId);
1242
+ if (!element) return Promise.resolve(false);
1243
+ element.scrollIntoView({ behavior: effectiveBehavior, block: "start" });
1244
+ return Promise.resolve(true);
1245
+ }
1246
+ function resolveScrollContainer(c) {
1247
+ if (!c) return null;
1248
+ if (typeof c === "function") return c();
1249
+ if ("current" in c) return c.current;
1250
+ return c;
1251
+ }
1252
+
1253
+ // src/core/strategies/scroll/wizard-smooth-scroll.strategy.ts
1254
+ var WizardSmoothScrollStrategy = class extends WizardScrollStrategy {
1255
+ constructor(wizardConfig) {
1256
+ super();
1257
+ this.wizardConfig = wizardConfig;
1258
+ }
1259
+ wizardConfig;
1260
+ onNavigate(navigation) {
1261
+ const tree = WizardScrollStrategy.projectToTree(navigation.to);
1262
+ if (!tree) return Promise.resolve(false);
1263
+ const isFirstStepOfCategory = tree.lastActiveStep?.id === tree.activeCategory.firstStep?.id;
1264
+ const stepId = isFirstStepOfCategory ? null : tree.lastActiveStep?.id;
1265
+ const useInstant = navigation.isCategoryChanging || (this.wizardConfig?.isMobile ?? false);
1266
+ return scrollToStep(stepId, useInstant ? "auto" : "smooth", this.container);
1267
+ }
1268
+ };
1269
+
1270
+ // src/core/strategies/visibility/wizard-visibility.strategy.ts
1271
+ var WizardVisibilityStrategy = class {
1272
+ };
1273
+
1274
+ // src/core/strategies/visibility/wizard-default-visibility.strategy.ts
1275
+ var WizardDefaultVisibilityStrategy = class extends WizardVisibilityStrategy {
1276
+ constructor(wizardConfig) {
1277
+ super();
1278
+ this.wizardConfig = wizardConfig;
1279
+ }
1280
+ wizardConfig;
1281
+ isStepVisible(step, tree) {
1282
+ const isShowStep = step.isActive && step.isShow;
1283
+ const isLastActiveStep = step.id === tree.lastActiveStep?.id;
1284
+ return this.wizardConfig.isMobile ? isShowStep && isLastActiveStep : isShowStep;
1285
+ }
1286
+ };
1287
+
1288
+ // src/core/strategies/visibility/wizard-last-active-visibility.strategy.ts
1289
+ var WizardLastActiveVisibilityStrategy = class extends WizardVisibilityStrategy {
1290
+ isStepVisible(step, tree) {
1291
+ const isShowStep = step.isActive && step.isShow;
1292
+ const isLastActiveStep = step.id === tree.lastActiveStep?.id;
1293
+ return isShowStep && isLastActiveStep;
1294
+ }
1295
+ };
1296
+
1297
+ // src/core/wizard-default.config.ts
1298
+ var passResolver = () => true;
1299
+ var wizardDefaultConfig = {
1300
+ backBtnClass: "",
1301
+ backBtnText: "Back",
1302
+ doneDotText: "Finish",
1303
+ enableTracing: "none",
1304
+ finishText: "Finish",
1305
+ headerI18n: null,
1306
+ isActiveCategoryClickReset: true,
1307
+ isBackResetCompleted: true,
1308
+ isDoneDot: false,
1309
+ isShowFirstStepOnCategoryChange: false,
1310
+ isShowWizardHeader: true,
1311
+ isShowWizardHeaderSteps: true,
1312
+ nextBtnClass: "",
1313
+ nextBtnText: "Next",
1314
+ nextCategoryText: "Continue",
1315
+ resolveCategoryChange: passResolver,
1316
+ resolveCategoryComplete: passResolver,
1317
+ resolveCategoryEnter: passResolver,
1318
+ resolveStepChange: passResolver,
1319
+ silenceErrors: false,
1320
+ stickyHeader: true
1321
+ };
1322
+
1323
+ // src/core/wizard-config.ts
1324
+ var WizardConfig = class {
1325
+ /**
1326
+ * Whether the wizard is rendering on a mobile viewport. Used by the default
1327
+ * visibility and scroll strategies. The React adapter (Phase 15) updates this
1328
+ * via a media-query subscription; in plain Node (tests/SSR), it stays `false`.
1329
+ */
1330
+ isMobile = false;
1331
+ get options() {
1332
+ return this.current;
1333
+ }
1334
+ current;
1335
+ disposed = false;
1336
+ listeners = /* @__PURE__ */ new Set();
1337
+ notifyScheduled = false;
1338
+ constructor(overrides = {}) {
1339
+ this.current = { ...wizardDefaultConfig, ...overrides };
1340
+ }
1341
+ dispose() {
1342
+ this.disposed = true;
1343
+ this.listeners.clear();
1344
+ }
1345
+ getOptions() {
1346
+ return this.current;
1347
+ }
1348
+ setOptions(options) {
1349
+ if (this.disposed) return this;
1350
+ let changed = false;
1351
+ for (const k of Object.keys(options)) {
1352
+ if (!Object.is(options[k], this.current[k])) {
1353
+ changed = true;
1354
+ break;
1355
+ }
1356
+ }
1357
+ if (!changed) return this;
1358
+ this.current = { ...this.current, ...options };
1359
+ this.scheduleNotify();
1360
+ return this;
1361
+ }
1362
+ subscribe(listener) {
1363
+ if (this.disposed) return () => {
1364
+ };
1365
+ this.listeners.add(listener);
1366
+ return () => {
1367
+ this.listeners.delete(listener);
1368
+ };
1369
+ }
1370
+ scheduleNotify() {
1371
+ if (this.notifyScheduled) return;
1372
+ this.notifyScheduled = true;
1373
+ queueMicrotask(() => {
1374
+ this.notifyScheduled = false;
1375
+ if (this.disposed) return;
1376
+ const snapshot = this.current;
1377
+ for (const listener of [...this.listeners]) {
1378
+ listener(snapshot);
1379
+ }
1380
+ });
1381
+ }
1382
+ };
1383
+
1384
+ // src/core/wizard-log.ts
1385
+ var WizardLog = class {
1386
+ constructor(wizardConfig) {
1387
+ this.wizardConfig = wizardConfig;
1388
+ }
1389
+ wizardConfig;
1390
+ error(e) {
1391
+ if (!(e instanceof WizardError)) {
1392
+ console.error(e);
1393
+ return;
1394
+ }
1395
+ if (this.wizardConfig.getOptions().silenceErrors) {
1396
+ return;
1397
+ }
1398
+ console.error(e);
1399
+ }
1400
+ trace(...args) {
1401
+ if (this.wizardConfig.getOptions().enableTracing !== "none") {
1402
+ console.log(...args);
1403
+ }
1404
+ }
1405
+ };
1406
+
1407
+ // src/core/wizard-store.ts
1408
+ var WizardStore = class {
1409
+ constructor(storagePrefix) {
1410
+ this.storagePrefix = storagePrefix;
1411
+ this.initOnce = new Promise((resolve) => {
1412
+ this.resolveInitOnce = resolve;
1413
+ });
1414
+ }
1415
+ storagePrefix;
1416
+ initOnce;
1417
+ get shapeId() {
1418
+ return this._shapeId ?? "";
1419
+ }
1420
+ _shapeId = null;
1421
+ current = null;
1422
+ disposed = false;
1423
+ initialTree = null;
1424
+ listeners = /* @__PURE__ */ new Set();
1425
+ resolveInitOnce;
1426
+ dispose() {
1427
+ this.disposed = true;
1428
+ this.listeners.clear();
1429
+ }
1430
+ getTreeSnapshot() {
1431
+ if (!this.current) {
1432
+ throw new WizardInitializationError("[WizardStore] getTreeSnapshot called before init");
1433
+ }
1434
+ return this.current;
1435
+ }
1436
+ init(initialTree) {
1437
+ if (this.disposed) return;
1438
+ if (this.initialTree) {
1439
+ console.warn("[WizardStore] init() called more than once; ignoring subsequent call.");
1440
+ return;
1441
+ }
1442
+ this.initialTree = initialTree;
1443
+ this._shapeId = getShapeId(initialTree.state, this.storagePrefix);
1444
+ this.setState(initialTree);
1445
+ this.resolveInitOnce(initialTree);
1446
+ }
1447
+ setState(tree) {
1448
+ if (this.disposed) return;
1449
+ if (!this.initialTree) {
1450
+ throw new WizardInitializationError("[WizardStore] setState called before init");
1451
+ }
1452
+ if (this.current === tree) return;
1453
+ this.current = tree;
1454
+ for (const listener of [...this.listeners]) {
1455
+ listener();
1456
+ }
1457
+ }
1458
+ subscribe = (listener) => {
1459
+ this.listeners.add(listener);
1460
+ return () => {
1461
+ this.listeners.delete(listener);
1462
+ };
1463
+ };
1464
+ };
1465
+
1466
+ // src/core/wizard-engine.ts
1467
+ var WizardEngine = class {
1468
+ get configOptions() {
1469
+ return this.config.options;
1470
+ }
1471
+ get isInitialized() {
1472
+ return this.initialized;
1473
+ }
1474
+ get lastActiveStep() {
1475
+ return this.tree.lastActiveStep;
1476
+ }
1477
+ get progress() {
1478
+ return this.progressStrategy.getProgress(this.tree);
1479
+ }
1480
+ get shapeId() {
1481
+ return this.store.shapeId;
1482
+ }
1483
+ get steps() {
1484
+ return this.tree.stepMap;
1485
+ }
1486
+ get tree() {
1487
+ return this.store.getTreeSnapshot();
1488
+ }
1489
+ committed = false;
1490
+ // commitRegistration() entered
1491
+ config;
1492
+ disposed = false;
1493
+ initialBranch;
1494
+ initialized = false;
1495
+ // store.init() ran successfully
1496
+ initialState;
1497
+ initService;
1498
+ listeners;
1499
+ log;
1500
+ // ------------- snapshots -------------
1501
+ navService;
1502
+ pendingPostInitTasks = [];
1503
+ progressStrategy;
1504
+ scrollStrategy;
1505
+ stateService;
1506
+ store;
1507
+ visibilityStrategy;
1508
+ constructor(options) {
1509
+ this.config = new WizardConfig(options.config ?? {});
1510
+ const storagePrefix = options.config?.storagePrefix ?? void 0;
1511
+ this.store = new WizardStore(storagePrefix);
1512
+ this.log = new WizardLog(this.config);
1513
+ this.scrollStrategy = options.strategies?.scroll ?? new WizardSmoothScrollStrategy(this.config);
1514
+ if (options.scrollContainer !== void 0) {
1515
+ this.scrollStrategy.setContainer(options.scrollContainer);
1516
+ }
1517
+ this.visibilityStrategy = options.strategies?.visibility ?? new WizardDefaultVisibilityStrategy(this.config);
1518
+ this.progressStrategy = options.strategies?.progress ?? new WizardActiveProgressStrategy();
1519
+ this.stateService = new WizardStateService(this.store, this.config);
1520
+ this.navService = new WizardNavigationService(
1521
+ this.store,
1522
+ this.config,
1523
+ this.stateService,
1524
+ this.scrollStrategy
1525
+ );
1526
+ const initializers = options.initializers ?? [new WizardDefaultInitializer()];
1527
+ this.initService = new WizardInitializerService(this.log, initializers);
1528
+ this.listeners = options.listeners ?? [];
1529
+ this.initialState = options.state;
1530
+ this.initialBranch = options.activeBranch;
1531
+ }
1532
+ // ------------- React adapter wiring -------------
1533
+ back() {
1534
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.prev()));
1535
+ }
1536
+ backCategory() {
1537
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.prevCategory()));
1538
+ }
1539
+ async commitRegistration() {
1540
+ if (this.disposed) return;
1541
+ if (this.committed) return;
1542
+ this.committed = true;
1543
+ const tree = await this.initService.getInitialState(this.initialState, this.initialBranch);
1544
+ if (this.disposed) {
1545
+ for (const task of this.pendingPostInitTasks.splice(0)) task.resolve(false);
1546
+ return;
1547
+ }
1548
+ this.store.init(tree);
1549
+ this.initialized = true;
1550
+ for (const listener of this.listeners) {
1551
+ try {
1552
+ listener.onInit(tree);
1553
+ } catch (e) {
1554
+ this.log.error(e);
1555
+ }
1556
+ }
1557
+ this.store.subscribe(() => {
1558
+ const current = this.store.getTreeSnapshot();
1559
+ for (const listener of this.listeners) {
1560
+ try {
1561
+ listener.onTreeChange(current);
1562
+ } catch (e) {
1563
+ this.log.error(e);
1564
+ }
1565
+ }
1566
+ });
1567
+ this.navService.on("*", (event) => {
1568
+ for (const listener of this.listeners) {
1569
+ try {
1570
+ listener.onEvent(event);
1571
+ } catch (e) {
1572
+ this.log.error(e);
1573
+ }
1574
+ }
1575
+ });
1576
+ for (const task of this.pendingPostInitTasks.splice(0)) {
1577
+ task.run();
1578
+ }
1579
+ }
1580
+ // ------------- bootstrap -------------
1581
+ completeWizard() {
1582
+ return this.runOrQueue(() => this.navService.completeWizard());
1583
+ }
1584
+ // ------------- imperative API -------------
1585
+ dispose() {
1586
+ if (this.disposed) return;
1587
+ this.disposed = true;
1588
+ for (const task of this.pendingPostInitTasks.splice(0)) task.resolve(false);
1589
+ const lastTree = this.initialized ? this.store.getTreeSnapshot() : null;
1590
+ for (const listener of this.listeners) {
1591
+ try {
1592
+ listener.onDestroy(lastTree);
1593
+ } catch (e) {
1594
+ this.log.error(e);
1595
+ }
1596
+ }
1597
+ this.navService.dispose();
1598
+ this.store.dispose();
1599
+ this.config.dispose();
1600
+ }
1601
+ exitWizard() {
1602
+ return this.runOrQueue(() => this.navService.exitWizard());
1603
+ }
1604
+ getTreeSnapshot = () => this.store.getTreeSnapshot();
1605
+ go(tree) {
1606
+ return this.runOrQueue(() => this.navService.navigate(tree));
1607
+ }
1608
+ goToCategory(category) {
1609
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.goToCategory(category)));
1610
+ }
1611
+ hideCategory(category) {
1612
+ return this.runOrQueue(() => this.navService.hideCategory(category));
1613
+ }
1614
+ hideStep(step) {
1615
+ return this.runOrQueue(() => this.navService.hideStep(step));
1616
+ }
1617
+ isStepVisible(stepId) {
1618
+ const tree = this.store.getTreeSnapshot();
1619
+ const step = tree.stepMap[stepId];
1620
+ if (!step) return false;
1621
+ return this.visibilityStrategy.isStepVisible(step, tree);
1622
+ }
1623
+ next(branch) {
1624
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.next(branch ?? null)));
1625
+ }
1626
+ nextCategory(branch) {
1627
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.nextCategory(branch ?? null)));
1628
+ }
1629
+ on(type, handler) {
1630
+ return this.navService.on(type, handler);
1631
+ }
1632
+ resetActiveCategory(from = 0) {
1633
+ return this.runOrQueue(
1634
+ () => this.navService.navigate(this.stateService.resetCategory(this.tree.activeCategory.id, from))
1635
+ );
1636
+ }
1637
+ resetCategory(category, from = 0) {
1638
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.resetCategory(category, from)));
1639
+ }
1640
+ resetWizard() {
1641
+ return this.runOrQueue(() => this.navService.navigate(this.stateService.resetTree()));
1642
+ }
1643
+ scrollToTop(behavior = "smooth") {
1644
+ return this.scrollStrategy.scrollToTop(behavior);
1645
+ }
1646
+ showCategory(category) {
1647
+ return this.runOrQueue(() => this.navService.showCategory(category));
1648
+ }
1649
+ showStep(step) {
1650
+ return this.runOrQueue(() => this.navService.showStep(step));
1651
+ }
1652
+ skipActiveCategory() {
1653
+ return this.runOrQueue(
1654
+ () => this.navService.navigate(this.stateService.skipCategory(this.tree.activeCategory.id))
1655
+ );
1656
+ }
1657
+ skipCategory(category) {
1658
+ return this.runOrQueue(
1659
+ () => this.navService.navigate(this.stateService.skipCategory(category ?? this.tree.activeCategory.id))
1660
+ );
1661
+ }
1662
+ subscribe = (listener) => this.store.subscribe(listener);
1663
+ toggleCategory(category, show) {
1664
+ return show ? this.showCategory(category) : this.hideCategory(category);
1665
+ }
1666
+ // ------------- lifecycle -------------
1667
+ toggleStep(step, show) {
1668
+ return show ? this.showStep(step) : this.hideStep(step);
1669
+ }
1670
+ // ------------- internal -------------
1671
+ runOrQueue(action) {
1672
+ if (this.disposed) {
1673
+ return Promise.resolve(false);
1674
+ }
1675
+ if (!this.initialized) {
1676
+ return new Promise((resolve, reject) => {
1677
+ this.pendingPostInitTasks.push({
1678
+ resolve,
1679
+ run: () => {
1680
+ action().then(resolve, reject);
1681
+ }
1682
+ });
1683
+ });
1684
+ }
1685
+ return action();
1686
+ }
1687
+ };
1688
+
1689
+ // src/enums/wizard-category-direction.enum.ts
1690
+ var WizardCategoryDirection = /* @__PURE__ */ ((WizardCategoryDirection2) => {
1691
+ WizardCategoryDirection2["Backward"] = "backward";
1692
+ WizardCategoryDirection2["Forward"] = "forward";
1693
+ return WizardCategoryDirection2;
1694
+ })(WizardCategoryDirection || {});
1695
+
1696
+ // src/react/components-provider.tsx
1697
+ import { createContext, useContext } from "react";
1698
+ import { jsx } from "react/jsx-runtime";
1699
+ var FallbackButton = (props) => {
1700
+ const { children, asChild: _asChild, variant: _variant, size: _size, ...rest } = props;
1701
+ return /* @__PURE__ */ jsx("button", { ...rest, type: props.type ?? "button", children });
1702
+ };
1703
+ var FallbackBackArrowIcon = ({ className }) => /* @__PURE__ */ jsx("svg", { className, width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M10 4 L6 8 L10 12", stroke: "currentColor", strokeWidth: "1.5", fill: "none" }) });
1704
+ var defaults = { Button: FallbackButton, BackArrowIcon: FallbackBackArrowIcon };
1705
+ var WizardComponentsContext = createContext(defaults);
1706
+ function WizardComponentsProvider(props) {
1707
+ const { Button, BackArrowIcon, children } = props;
1708
+ const value = {
1709
+ Button: Button ?? defaults.Button,
1710
+ BackArrowIcon: BackArrowIcon ?? defaults.BackArrowIcon
1711
+ };
1712
+ return /* @__PURE__ */ jsx(WizardComponentsContext.Provider, { value, children });
1713
+ }
1714
+ function useWizardComponents() {
1715
+ return useContext(WizardComponentsContext);
1716
+ }
1717
+
1718
+ // src/react/use-wizard.ts
1719
+ import { useContext as useContext2, useSyncExternalStore } from "react";
1720
+
1721
+ // src/react/context.ts
1722
+ import { createContext as createContext2 } from "react";
1723
+ var WizardEngineContext = createContext2(null);
1724
+
1725
+ // src/react/use-wizard.ts
1726
+ function useWizard() {
1727
+ const engine = useContext2(WizardEngineContext);
1728
+ if (!engine) {
1729
+ throw new WizardError("useWizard must be used inside <WizardProvider>");
1730
+ }
1731
+ useSyncExternalStore(engine.subscribe, engine.getTreeSnapshot, engine.getTreeSnapshot);
1732
+ return engine;
1733
+ }
1734
+
1735
+ // src/react/components/wizard-action-button.tsx
1736
+ import { Slot } from "@radix-ui/react-slot";
1737
+ import { jsx as jsx2 } from "react/jsx-runtime";
1738
+ function WizardActionButton(props) {
1739
+ const { Button } = useWizardComponents();
1740
+ if (props.asChild) {
1741
+ return /* @__PURE__ */ jsx2(Slot, { className: props.className, onClick: props.onClick, children: props.children });
1742
+ }
1743
+ return /* @__PURE__ */ jsx2(Button, { className: props.className, onClick: props.onClick, type: "button", variant: props.variant, children: props.children ?? props.defaultLabel });
1744
+ }
1745
+
1746
+ // src/react/components/wizard-back.tsx
1747
+ import { jsx as jsx3 } from "react/jsx-runtime";
1748
+ function WizardBack(props) {
1749
+ const wizard = useWizard();
1750
+ return /* @__PURE__ */ jsx3(
1751
+ WizardActionButton,
1752
+ {
1753
+ asChild: props.asChild,
1754
+ className: props.className,
1755
+ defaultLabel: "Back",
1756
+ onClick: () => void wizard.back(),
1757
+ variant: "ghost",
1758
+ children: props.children
1759
+ }
1760
+ );
1761
+ }
1762
+
1763
+ // src/react/components/wizard-category.tsx
1764
+ import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
1765
+ function WizardCategory(props) {
1766
+ const wizard = useWizard();
1767
+ if (wizard.tree.activeCategory.id !== props.categoryId) return null;
1768
+ return /* @__PURE__ */ jsx4(Fragment, { children: props.children });
1769
+ }
1770
+
1771
+ // src/utils/cn.ts
1772
+ import { clsx } from "clsx";
1773
+ import { twMerge } from "tailwind-merge";
1774
+ function cn(...inputs) {
1775
+ return twMerge(clsx(...inputs));
1776
+ }
1777
+
1778
+ // src/react/components/wizard-stepper-dot.tsx
1779
+ import { CheckIcon } from "lucide-react";
1780
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
1781
+ function WizardStepperDot(props) {
1782
+ const { clickable = false, isLast = false, label, onClick, progress, state } = props;
1783
+ const isInteractive = clickable && !!onClick;
1784
+ return /* @__PURE__ */ jsxs("li", { className: "relative flex flex-1 items-center", "data-state": state, children: [
1785
+ /* @__PURE__ */ jsx5(
1786
+ "button",
1787
+ {
1788
+ "aria-current": state === "active" ? "step" : void 0,
1789
+ "aria-label": label ?? state,
1790
+ className: cn(
1791
+ "relative flex size-3 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
1792
+ state === "pending" && "border-border bg-background",
1793
+ state === "active" && "border-success-text bg-success-text ring-2 ring-success-border/40 ring-offset-2 ring-offset-background",
1794
+ state === "completed" && "border-success-border bg-success-text",
1795
+ state === "skipped" && "border-border bg-background opacity-60",
1796
+ isInteractive && "cursor-pointer hover:bg-success-bg",
1797
+ !isInteractive && "cursor-default"
1798
+ ),
1799
+ disabled: !isInteractive,
1800
+ onClick: isInteractive ? onClick : void 0,
1801
+ type: "button",
1802
+ children: state === "completed" && /* @__PURE__ */ jsx5(CheckIcon, { className: "size-2 text-background" })
1803
+ }
1804
+ ),
1805
+ !isLast && /* @__PURE__ */ jsx5("span", { "aria-hidden": true, className: "relative ml-1 mr-1 h-px flex-1 bg-border", children: /* @__PURE__ */ jsx5(
1806
+ "span",
1807
+ {
1808
+ className: "absolute inset-y-0 left-0 bg-success-border transition-[width]",
1809
+ style: { width: `${Math.max(0, Math.min(100, progress))}%` }
1810
+ }
1811
+ ) }),
1812
+ label && /* @__PURE__ */ jsx5(
1813
+ "span",
1814
+ {
1815
+ className: cn(
1816
+ "absolute -top-6 left-0 -translate-x-[calc(50%-0.375rem)] text-sm font-semibold whitespace-nowrap",
1817
+ state === "active" && "text-success-text",
1818
+ state !== "active" && "text-muted-foreground"
1819
+ ),
1820
+ children: label
1821
+ }
1822
+ )
1823
+ ] });
1824
+ }
1825
+
1826
+ // src/react/components/wizard-stepper.tsx
1827
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1828
+ function WizardStepper(props) {
1829
+ const { canAccessCategory, categories, doneDot, headerI18n, onCategoryClick, progress } = props;
1830
+ const lastIdx = categories.length - 1;
1831
+ return /* @__PURE__ */ jsxs2("ol", { "aria-label": "Wizard progress", className: "relative flex flex-1 items-center pt-6", children: [
1832
+ categories.map((category, idx) => {
1833
+ const isLast = idx === lastIdx && !doneDot;
1834
+ const clickable = canAccessCategory?.(category) ?? false;
1835
+ return /* @__PURE__ */ jsx6(
1836
+ WizardStepperDot,
1837
+ {
1838
+ clickable,
1839
+ isLast,
1840
+ label: headerI18n?.[category.id],
1841
+ onClick: onCategoryClick ? () => onCategoryClick(category) : void 0,
1842
+ progress: progress[category.id] ?? 0,
1843
+ state: getDotState(category, progress[category.id] ?? 0)
1844
+ },
1845
+ category.id
1846
+ );
1847
+ }),
1848
+ doneDot && /* @__PURE__ */ jsx6(WizardStepperDot, { isLast: true, label: doneDot.text, progress: 0, state: "pending" })
1849
+ ] });
1850
+ }
1851
+ function getDotState(category, progress) {
1852
+ if (category.isActive) return "active";
1853
+ if (progress >= 100) return "completed";
1854
+ if (category.isSkipped) return "skipped";
1855
+ return "pending";
1856
+ }
1857
+
1858
+ // src/react/components/wizard-header.tsx
1859
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1860
+ function WizardHeader() {
1861
+ const wizard = useWizard();
1862
+ const tree = wizard.tree;
1863
+ const config = wizard.configOptions;
1864
+ if (!config.isShowWizardHeader) return null;
1865
+ const oneCategory = tree.activeBranch.shownCategories.length === 1 && !config.isDoneDot;
1866
+ const stepHeaderEnabled = true;
1867
+ const showSteps = config.isShowWizardHeaderSteps && stepHeaderEnabled && !oneCategory;
1868
+ const canAccessCategory = (category) => {
1869
+ if (config.isBackResetCompleted) return false;
1870
+ if (category.isCompleted) return true;
1871
+ const idx = tree.activeBranch.shownCategories.indexOf(category);
1872
+ return !!tree.activeBranch.shownCategories[idx - 1]?.isCompleted;
1873
+ };
1874
+ const onCategoryClick = (category) => {
1875
+ if (config.isBackResetCompleted) return;
1876
+ const isCategoryStart = category.activeSteps.length === 1;
1877
+ if (config.isActiveCategoryClickReset && category.isActive && !isCategoryStart) {
1878
+ void wizard.resetCategory(category.id);
1879
+ return;
1880
+ }
1881
+ if (!category.isActive && category.isCompleted) {
1882
+ void wizard.goToCategory(category.id);
1883
+ return;
1884
+ }
1885
+ if (!category.isActive && canAccessCategory(category)) {
1886
+ void wizard.goToCategory(category.id);
1887
+ }
1888
+ };
1889
+ return /* @__PURE__ */ jsxs3(
1890
+ "header",
1891
+ {
1892
+ className: cn(
1893
+ "relative mb-8 flex items-center gap-4 border-b border-border bg-background px-4 py-3",
1894
+ config.stickyHeader && "sticky top-0 z-10"
1895
+ ),
1896
+ children: [
1897
+ /* @__PURE__ */ jsx7(WizardBack, {}),
1898
+ showSteps && /* @__PURE__ */ jsx7(
1899
+ WizardStepper,
1900
+ {
1901
+ canAccessCategory,
1902
+ categories: tree.activeBranch.shownCategories,
1903
+ doneDot: config.isDoneDot ? { text: config.doneDotText } : void 0,
1904
+ headerI18n: config.headerI18n,
1905
+ onCategoryClick,
1906
+ progress: wizard.progress
1907
+ }
1908
+ )
1909
+ ]
1910
+ }
1911
+ );
1912
+ }
1913
+
1914
+ // src/react/components/wizard-next.tsx
1915
+ import { jsx as jsx8 } from "react/jsx-runtime";
1916
+ function WizardNext(props) {
1917
+ const wizard = useWizard();
1918
+ const defaultLabel = !props.asChild && props.children === void 0 ? getNextLabel(wizard) : "";
1919
+ return /* @__PURE__ */ jsx8(
1920
+ WizardActionButton,
1921
+ {
1922
+ asChild: props.asChild,
1923
+ className: props.className,
1924
+ defaultLabel,
1925
+ onClick: () => void wizard.next(),
1926
+ children: props.children
1927
+ }
1928
+ );
1929
+ }
1930
+ function getNextLabel(wizard) {
1931
+ const config = wizard.configOptions;
1932
+ const tree = wizard.tree;
1933
+ const activeStep = tree.lastActiveStep;
1934
+ const category = tree.categoryMap[activeStep.categoryId];
1935
+ const isLastStep = category.lastStep.id === activeStep.id;
1936
+ const isLastCategory = tree.activeBranch.lastCategory.id === category.id;
1937
+ if (!isLastStep) return config.nextBtnText;
1938
+ return isLastCategory ? config.finishText : config.nextCategoryText;
1939
+ }
1940
+
1941
+ // src/react/components/wizard-rail.tsx
1942
+ import { CheckIcon as CheckIcon2 } from "lucide-react";
1943
+
1944
+ // src/react/hooks/use-wizard-categories-view.ts
1945
+ var EMPTY_SUB_STEPS = [];
1946
+ function useWizardCategoriesView() {
1947
+ const wizard = useWizard();
1948
+ return wizard.tree.activeBranch.shownCategories.map((category) => projectCategory(category));
1949
+ }
1950
+ function getCategoryState(category) {
1951
+ if (category.isActive) return "active";
1952
+ if (category.isCompleted) return "completed";
1953
+ if (category.isSkipped) return "skipped";
1954
+ return "pending";
1955
+ }
1956
+ function projectCategory(category) {
1957
+ const shown = category.shownSteps;
1958
+ const subSteps = shown.length > 1 ? shown.slice().sort((a, b) => a.htmlIndex - b.htmlIndex).map((step) => ({ id: step.id, isActive: step.isActive })) : EMPTY_SUB_STEPS;
1959
+ return {
1960
+ id: category.id,
1961
+ isActive: category.isActive,
1962
+ state: getCategoryState(category),
1963
+ subSteps
1964
+ };
1965
+ }
1966
+
1967
+ // src/react/components/wizard-rail.tsx
1968
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1969
+ function WizardRail(props) {
1970
+ const { canAccessCategory, className, headerI18n, subStepLabels } = props;
1971
+ const wizard = useWizard();
1972
+ const view = useWizardCategoriesView();
1973
+ const labels = headerI18n ?? wizard.configOptions.headerI18n;
1974
+ const isClickable = (category) => canAccessCategory?.(category) ?? defaultClickable(category.state);
1975
+ return /* @__PURE__ */ jsx9("nav", { "aria-label": "Wizard steps", className: cn("flex flex-col gap-1", className), children: /* @__PURE__ */ jsx9("ol", { className: "relative flex flex-col gap-1", children: view.map((category, idx) => {
1976
+ const label = labels?.[category.id] ?? String(category.id);
1977
+ const clickable = isClickable(category);
1978
+ const isLast = idx === view.length - 1;
1979
+ const isPassed = category.state === "completed" || category.state === "skipped";
1980
+ return /* @__PURE__ */ jsxs4("li", { className: "relative", children: [
1981
+ !isLast && /* @__PURE__ */ jsx9(
1982
+ "span",
1983
+ {
1984
+ "aria-hidden": true,
1985
+ className: cn(
1986
+ "pointer-events-none absolute top-7 -bottom-2 left-5 w-px -translate-x-1/2 transition-colors duration-200",
1987
+ isPassed ? "bg-success-border" : "bg-border"
1988
+ )
1989
+ }
1990
+ ),
1991
+ /* @__PURE__ */ jsxs4(
1992
+ "button",
1993
+ {
1994
+ "aria-current": category.isActive ? "step" : void 0,
1995
+ className: cn(
1996
+ "group flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm font-medium transition-[color,background-color,transform] duration-200 ease-(--ease-out-strong)",
1997
+ category.state === "active" && "bg-success-bg/60 text-success-text",
1998
+ category.state === "completed" && "text-foreground",
1999
+ category.state === "skipped" && "text-muted-foreground opacity-70",
2000
+ category.state === "pending" && "text-muted-foreground",
2001
+ clickable ? "cursor-pointer hover:bg-foreground/[0.06] motion-safe:active:scale-[0.97]" : "cursor-default"
2002
+ ),
2003
+ "data-state": category.state,
2004
+ disabled: !clickable,
2005
+ onClick: () => {
2006
+ void wizard.goToCategory(category.id);
2007
+ },
2008
+ type: "button",
2009
+ children: [
2010
+ /* @__PURE__ */ jsx9(RailDot, { state: category.state }),
2011
+ /* @__PURE__ */ jsx9("span", { children: label })
2012
+ ]
2013
+ }
2014
+ ),
2015
+ category.isActive && category.subSteps.length > 0 && /* @__PURE__ */ jsx9("ol", { className: "mt-1 ml-7 flex flex-col gap-1 border-l border-border pl-3", children: category.subSteps.map((step) => /* @__PURE__ */ jsx9(
2016
+ "li",
2017
+ {
2018
+ "aria-current": step.isActive ? "step" : void 0,
2019
+ className: cn(
2020
+ "rounded px-2 py-1 text-sm",
2021
+ step.isActive ? "text-success-text font-medium" : "text-muted-foreground"
2022
+ ),
2023
+ children: subStepLabels?.[step.id] ?? String(step.id)
2024
+ },
2025
+ step.id
2026
+ )) })
2027
+ ] }, category.id);
2028
+ }) }) });
2029
+ }
2030
+ function defaultClickable(state) {
2031
+ return state !== "pending";
2032
+ }
2033
+ function RailDot({ state }) {
2034
+ return /* @__PURE__ */ jsx9(
2035
+ "span",
2036
+ {
2037
+ "aria-hidden": true,
2038
+ className: cn(
2039
+ "flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
2040
+ state === "pending" && "border-border bg-background",
2041
+ state === "active" && "border-success-border bg-success-bg",
2042
+ state === "completed" && "border-success-border bg-success-text",
2043
+ state === "skipped" && "border-border bg-background opacity-60"
2044
+ ),
2045
+ children: state === "completed" && /* @__PURE__ */ jsx9(CheckIcon2, { className: "size-2.5 text-background motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-75 motion-safe:duration-200" })
2046
+ }
2047
+ );
2048
+ }
2049
+
2050
+ // src/react/components/wizard-step.tsx
2051
+ import { useCallback, useContext as useContext3, useSyncExternalStore as useSyncExternalStore2 } from "react";
2052
+ import { jsx as jsx10 } from "react/jsx-runtime";
2053
+ function WizardStep(props) {
2054
+ const engine = useContext3(WizardEngineContext);
2055
+ if (!engine) {
2056
+ throw new WizardError("WizardStep must be used inside <WizardProvider>");
2057
+ }
2058
+ const getSnapshot = useCallback(() => engine.isStepVisible(props.id), [engine, props.id]);
2059
+ const isVisible = useSyncExternalStore2(engine.subscribe, getSnapshot, getSnapshot);
2060
+ if (!isVisible) return null;
2061
+ return /* @__PURE__ */ jsx10(
2062
+ "div",
2063
+ {
2064
+ className: "motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1 motion-safe:duration-300 motion-safe:ease-(--ease-out-strong)",
2065
+ id: props.id,
2066
+ children: props.children
2067
+ }
2068
+ );
2069
+ }
2070
+
2071
+ // src/react/provider.tsx
2072
+ import { useEffect, useState } from "react";
2073
+ import { jsx as jsx11 } from "react/jsx-runtime";
2074
+ function WizardProvider(props) {
2075
+ const { activeBranch, children, config, initializers, listeners, scrollContainer, state, strategies } = props;
2076
+ const [bootOptions] = useState(() => ({
2077
+ activeBranch,
2078
+ config,
2079
+ initializers,
2080
+ listeners,
2081
+ scrollContainer,
2082
+ state,
2083
+ strategies
2084
+ }));
2085
+ const [engine, setEngine] = useState(null);
2086
+ useEffect(() => {
2087
+ const newEngine = new WizardEngine(bootOptions);
2088
+ let cancelled = false;
2089
+ newEngine.commitRegistration().then(() => {
2090
+ if (!cancelled) setEngine(newEngine);
2091
+ });
2092
+ return () => {
2093
+ cancelled = true;
2094
+ newEngine.dispose();
2095
+ setEngine((prev) => prev === newEngine ? null : prev);
2096
+ };
2097
+ }, [bootOptions]);
2098
+ if (!engine) {
2099
+ return null;
2100
+ }
2101
+ return /* @__PURE__ */ jsx11(WizardEngineContext.Provider, { value: engine, children });
2102
+ }
2103
+
2104
+ // src/react/use-wizard-engine-ref.ts
2105
+ import { useEffect as useEffect2, useRef } from "react";
2106
+ function useWizardEngineRef() {
2107
+ return useRef(null);
2108
+ }
2109
+ function WizardEngineRefCapture({
2110
+ engineRef
2111
+ }) {
2112
+ const wizard = useWizard();
2113
+ useEffect2(() => {
2114
+ engineRef.current = wizard;
2115
+ return () => {
2116
+ engineRef.current = null;
2117
+ };
2118
+ }, [engineRef, wizard]);
2119
+ return null;
2120
+ }
2121
+
2122
+ // src/react/use-wizard-event.ts
2123
+ import { useContext as useContext4, useEffect as useEffect3, useRef as useRef2 } from "react";
2124
+ function useWizardEvent(type, handler) {
2125
+ const engine = useContext4(WizardEngineContext);
2126
+ if (!engine) {
2127
+ throw new WizardError("useWizardEvent must be used inside <WizardProvider>");
2128
+ }
2129
+ const handlerRef = useRef2(handler);
2130
+ useEffect3(() => {
2131
+ handlerRef.current = handler;
2132
+ });
2133
+ useEffect3(() => {
2134
+ return engine.on(type, (event) => handlerRef.current(event));
2135
+ }, [engine, type]);
2136
+ }
2137
+
2138
+ // src/react/use-wizard-step.ts
2139
+ import { useCallback as useCallback2, useContext as useContext5, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
2140
+ function useWizardStep(stepId) {
2141
+ const engine = useContext5(WizardEngineContext);
2142
+ if (!engine) {
2143
+ throw new WizardError("useWizardStep must be used inside <WizardProvider>");
2144
+ }
2145
+ const lastRef = useRef3(null);
2146
+ const getSnapshot = useCallback2(() => {
2147
+ const tree = engine.getTreeSnapshot();
2148
+ const next = tree.stepMap[stepId] ?? null;
2149
+ const prev = lastRef.current;
2150
+ if (prev !== null && next !== null && prev.isActive === next.isActive && prev.isShow === next.isShow && prev.isCompleted === next.isCompleted && prev.isSkipped === next.isSkipped) {
2151
+ return prev;
2152
+ }
2153
+ lastRef.current = next;
2154
+ return next;
2155
+ }, [engine, stepId]);
2156
+ return useSyncExternalStore3(engine.subscribe, getSnapshot, getSnapshot);
2157
+ }
2158
+ export {
2159
+ TypedEmitter,
2160
+ WizardActiveProgressStrategy,
2161
+ WizardActiveStepRule,
2162
+ WizardBack,
2163
+ WizardCategory,
2164
+ WizardCategoryDirection,
2165
+ WizardCompleteEvent,
2166
+ WizardCompleteTreeState,
2167
+ WizardComponentsProvider,
2168
+ WizardConfig,
2169
+ WizardDefaultInitializer,
2170
+ WizardDefaultVisibilityStrategy,
2171
+ WizardEngine,
2172
+ WizardEngineContext,
2173
+ WizardEngineRefCapture,
2174
+ WizardError,
2175
+ WizardEvent,
2176
+ WizardEventType,
2177
+ WizardExitEvent,
2178
+ WizardExitTreeState,
2179
+ WizardHeader,
2180
+ WizardInitializationError,
2181
+ WizardInitializer,
2182
+ WizardInitializerService,
2183
+ WizardLastActiveVisibilityStrategy,
2184
+ WizardListener,
2185
+ WizardLog,
2186
+ WizardNavigation,
2187
+ WizardNavigationCancelledEvent,
2188
+ WizardNavigationEndEvent,
2189
+ WizardNavigationError,
2190
+ WizardNavigationErrorEvent,
2191
+ WizardNavigationIgnoredEvent,
2192
+ WizardNavigationService,
2193
+ WizardNavigationStartEvent,
2194
+ WizardNext,
2195
+ WizardPassedPrevCategoriesRule,
2196
+ WizardPassedPrevStepsRule,
2197
+ WizardProgressStrategy,
2198
+ WizardProvider,
2199
+ WizardRail,
2200
+ WizardResolveEndEvent,
2201
+ WizardResolveStartEvent,
2202
+ WizardResolverError,
2203
+ WizardRule,
2204
+ WizardScrollEndEvent,
2205
+ WizardScrollStartEvent,
2206
+ WizardScrollStrategy,
2207
+ WizardShownActiveCategoryRule,
2208
+ WizardSingleActiveCategoryRule,
2209
+ WizardSmoothScrollStrategy,
2210
+ WizardStateService,
2211
+ WizardStep,
2212
+ WizardStepHideEvent,
2213
+ WizardStepShowEvent,
2214
+ WizardStepper,
2215
+ WizardStepperDot,
2216
+ WizardStore,
2217
+ WizardTreeStateBuilder,
2218
+ WizardVisibilityStrategy,
2219
+ buildWizardBranchState,
2220
+ buildWizardCategoryState,
2221
+ buildWizardTreeState,
2222
+ composeWizardProviders,
2223
+ scrollToStep,
2224
+ useWizard,
2225
+ useWizardCategoriesView,
2226
+ useWizardComponents,
2227
+ useWizardEngineRef,
2228
+ useWizardEvent,
2229
+ useWizardStep,
2230
+ withConfig,
2231
+ withInitializer,
2232
+ withListener,
2233
+ withProgressStrategy,
2234
+ withScrollContainer,
2235
+ withScrollStrategy,
2236
+ withVisibilityStrategy,
2237
+ wizardDefaultBranch,
2238
+ wizardDefaultConfig,
2239
+ wizardDefaultState,
2240
+ wizardRules
2241
+ };
2242
+ //# sourceMappingURL=index.js.map