webdrive 1.0.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,1395 @@
1
+ // src/core/EventEmitter.ts
2
+ var EventEmitter = class {
3
+ constructor() {
4
+ this.events = /* @__PURE__ */ new Map();
5
+ }
6
+ on(event, handler) {
7
+ let handlers = this.events.get(event);
8
+ if (!handlers) {
9
+ handlers = /* @__PURE__ */ new Set();
10
+ this.events.set(event, handlers);
11
+ }
12
+ handlers.add(handler);
13
+ }
14
+ off(event, handler) {
15
+ const handlers = this.events.get(event);
16
+ if (handlers) {
17
+ handlers.delete(handler);
18
+ if (handlers.size === 0) {
19
+ this.events.delete(event);
20
+ }
21
+ }
22
+ }
23
+ emit(event, ...args) {
24
+ const handlers = this.events.get(event);
25
+ if (handlers) {
26
+ const data = args[0];
27
+ for (const handler of Array.from(handlers)) {
28
+ try {
29
+ handler(data);
30
+ } catch (error) {
31
+ console.error(`[WebDrive] Error in "${String(event)}" event handler:`, error);
32
+ }
33
+ }
34
+ }
35
+ }
36
+ removeAllListeners() {
37
+ this.events.clear();
38
+ }
39
+ };
40
+
41
+ // src/storage/StorageManager.ts
42
+ var MemoryStorage = class {
43
+ constructor() {
44
+ this.memory = /* @__PURE__ */ new Map();
45
+ }
46
+ getItem(key) {
47
+ return this.memory.get(key) ?? null;
48
+ }
49
+ setItem(key, value) {
50
+ this.memory.set(key, value);
51
+ }
52
+ removeItem(key) {
53
+ this.memory.delete(key);
54
+ }
55
+ clear() {
56
+ this.memory.clear();
57
+ }
58
+ };
59
+ var LocalStorageAdapter = class {
60
+ constructor() {
61
+ this.fallback = new MemoryStorage();
62
+ }
63
+ isAvailable() {
64
+ if (typeof window === "undefined" || !window.localStorage) {
65
+ return false;
66
+ }
67
+ try {
68
+ const testKey = "__webdrive_storage_test__";
69
+ window.localStorage.setItem(testKey, "1");
70
+ window.localStorage.removeItem(testKey);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ getItem(key) {
77
+ if (!this.isAvailable()) {
78
+ return this.fallback.getItem(key);
79
+ }
80
+ try {
81
+ return window.localStorage.getItem(key);
82
+ } catch {
83
+ return this.fallback.getItem(key);
84
+ }
85
+ }
86
+ setItem(key, value) {
87
+ if (!this.isAvailable()) {
88
+ this.fallback.setItem(key, value);
89
+ return;
90
+ }
91
+ try {
92
+ window.localStorage.setItem(key, value);
93
+ } catch {
94
+ this.fallback.setItem(key, value);
95
+ }
96
+ }
97
+ removeItem(key) {
98
+ if (!this.isAvailable()) {
99
+ this.fallback.removeItem(key);
100
+ return;
101
+ }
102
+ try {
103
+ window.localStorage.removeItem(key);
104
+ } catch {
105
+ this.fallback.removeItem(key);
106
+ }
107
+ }
108
+ };
109
+ var StorageManager = class {
110
+ constructor(customStorage) {
111
+ this.prefix = "webdrive:tour:";
112
+ this.storage = customStorage ?? new LocalStorageAdapter();
113
+ }
114
+ getKey(id) {
115
+ return `${this.prefix}${id}`;
116
+ }
117
+ async isCompleted(id) {
118
+ if (!id) return false;
119
+ const value = await this.storage.getItem(this.getKey(id));
120
+ return value === "completed" || value === "true";
121
+ }
122
+ async markCompleted(id) {
123
+ if (!id) return;
124
+ await this.storage.setItem(this.getKey(id), "completed");
125
+ }
126
+ async reset(id) {
127
+ if (!id) return;
128
+ await this.storage.removeItem(this.getKey(id));
129
+ }
130
+ async resetAll() {
131
+ if (typeof window !== "undefined" && window.localStorage) {
132
+ try {
133
+ const keysToRemove = [];
134
+ for (let i = 0; i < window.localStorage.length; i++) {
135
+ const key = window.localStorage.key(i);
136
+ if (key && key.startsWith(this.prefix)) {
137
+ keysToRemove.push(key);
138
+ }
139
+ }
140
+ for (const key of keysToRemove) {
141
+ window.localStorage.removeItem(key);
142
+ }
143
+ } catch {
144
+ }
145
+ }
146
+ }
147
+ };
148
+
149
+ // src/dom/DOMUtils.ts
150
+ function resolveElement(target) {
151
+ if (!target) return null;
152
+ if (typeof target === "string") {
153
+ if (typeof document === "undefined") return null;
154
+ try {
155
+ const el = document.querySelector(target);
156
+ if (!el) {
157
+ console.warn(`[WebDrive] Target element "${target}" was not found.`);
158
+ return null;
159
+ }
160
+ return el;
161
+ } catch (error) {
162
+ console.warn(`[WebDrive] Invalid selector "${target}":`, error);
163
+ return null;
164
+ }
165
+ }
166
+ if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
167
+ return target;
168
+ }
169
+ return null;
170
+ }
171
+ function scrollIntoViewSafely(element, smooth = true) {
172
+ if (!element || typeof element.scrollIntoView !== "function") return;
173
+ try {
174
+ element.scrollIntoView({
175
+ behavior: smooth ? "smooth" : "auto",
176
+ block: "center",
177
+ inline: "nearest"
178
+ });
179
+ } catch {
180
+ try {
181
+ element.scrollIntoView(true);
182
+ } catch {
183
+ }
184
+ }
185
+ }
186
+ function getElementBoundingBox(element, padding = 0) {
187
+ const rect = element.getBoundingClientRect();
188
+ const top = rect.top - padding;
189
+ const left = rect.left - padding;
190
+ const width = rect.width + padding * 2;
191
+ const height = rect.height + padding * 2;
192
+ return {
193
+ top,
194
+ left,
195
+ width,
196
+ height,
197
+ right: left + width,
198
+ bottom: top + height
199
+ };
200
+ }
201
+ function getFocusableElements(container) {
202
+ const selector = [
203
+ 'a[href]:not([tabindex="-1"])',
204
+ 'button:not([disabled]):not([tabindex="-1"])',
205
+ 'textarea:not([disabled]):not([tabindex="-1"])',
206
+ 'input:not([disabled]):not([tabindex="-1"])',
207
+ 'select:not([disabled]):not([tabindex="-1"])',
208
+ '[tabindex]:not([tabindex="-1"])'
209
+ ].join(", ");
210
+ return Array.from(container.querySelectorAll(selector)).filter(
211
+ (el) => el.offsetParent !== null || el.getClientRects().length > 0
212
+ );
213
+ }
214
+ function removeElement(el) {
215
+ if (el && el.parentNode) {
216
+ el.parentNode.removeChild(el);
217
+ }
218
+ }
219
+
220
+ // src/dom/Overlay.ts
221
+ var Overlay = class {
222
+ constructor(config = {}) {
223
+ this.rootElement = null;
224
+ this.cutoutRect = null;
225
+ this.stageElement = null;
226
+ this.config = {
227
+ enabled: config.enabled ?? true,
228
+ opacity: config.opacity ?? 0.6,
229
+ color: config.color,
230
+ zIndex: config.zIndex ?? 1e5,
231
+ animate: config.animate ?? true,
232
+ allowClose: config.allowClose ?? true,
233
+ onOverlayClick: config.onOverlayClick
234
+ };
235
+ this.maskId = `webdrive-mask-${Math.random().toString(36).slice(2, 9)}`;
236
+ }
237
+ mount() {
238
+ if (typeof document === "undefined") return;
239
+ if (this.rootElement && this.rootElement.isConnected) return;
240
+ const root = document.createElement("div");
241
+ root.setAttribute("data-webdrive-root", "");
242
+ root.className = "webdrive-root";
243
+ root.style.setProperty("--webdrive-z-index", String(this.config.zIndex));
244
+ if (this.config.opacity !== void 0) {
245
+ root.style.setProperty("--webdrive-overlay-opacity", String(this.config.opacity));
246
+ }
247
+ if (this.config.color) {
248
+ root.style.setProperty("--webdrive-overlay", this.config.color);
249
+ }
250
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
251
+ svg.setAttribute("data-webdrive-overlay", "");
252
+ svg.setAttribute("class", "webdrive-overlay");
253
+ svg.setAttribute("width", "100%");
254
+ svg.setAttribute("height", "100%");
255
+ if (!this.config.enabled) {
256
+ svg.style.display = "none";
257
+ }
258
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
259
+ const mask = document.createElementNS("http://www.w3.org/2000/svg", "mask");
260
+ mask.setAttribute("id", this.maskId);
261
+ const maskBg = document.createElementNS("http://www.w3.org/2000/svg", "rect");
262
+ maskBg.setAttribute("x", "0");
263
+ maskBg.setAttribute("y", "0");
264
+ maskBg.setAttribute("width", "100%");
265
+ maskBg.setAttribute("height", "100%");
266
+ maskBg.setAttribute("fill", "#ffffff");
267
+ const cutout = document.createElementNS("http://www.w3.org/2000/svg", "rect");
268
+ cutout.setAttribute("data-webdrive-cutout", "");
269
+ cutout.setAttribute("class", "webdrive-cutout");
270
+ cutout.setAttribute("x", "0");
271
+ cutout.setAttribute("y", "0");
272
+ cutout.setAttribute("width", "0");
273
+ cutout.setAttribute("height", "0");
274
+ cutout.setAttribute("rx", "4");
275
+ cutout.setAttribute("ry", "4");
276
+ cutout.setAttribute("fill", "#000000");
277
+ mask.appendChild(maskBg);
278
+ mask.appendChild(cutout);
279
+ defs.appendChild(mask);
280
+ svg.appendChild(defs);
281
+ const overlayRect = document.createElementNS(
282
+ "http://www.w3.org/2000/svg",
283
+ "rect"
284
+ );
285
+ overlayRect.setAttribute("x", "0");
286
+ overlayRect.setAttribute("y", "0");
287
+ overlayRect.setAttribute("width", "100%");
288
+ overlayRect.setAttribute("height", "100%");
289
+ overlayRect.setAttribute("fill", "currentColor");
290
+ overlayRect.setAttribute("mask", `url(#${this.maskId})`);
291
+ svg.appendChild(overlayRect);
292
+ const stage = document.createElement("div");
293
+ stage.setAttribute("data-webdrive-stage", "");
294
+ stage.className = "webdrive-stage";
295
+ if (this.config.allowClose && this.config.onOverlayClick) {
296
+ svg.addEventListener("click", (e) => {
297
+ if (e.target === svg || e.target === overlayRect) {
298
+ this.config.onOverlayClick?.();
299
+ }
300
+ });
301
+ }
302
+ root.appendChild(svg);
303
+ root.appendChild(stage);
304
+ document.body.appendChild(root);
305
+ this.rootElement = root;
306
+ this.cutoutRect = cutout;
307
+ this.stageElement = stage;
308
+ }
309
+ update(rect, animate = true) {
310
+ if (!this.cutoutRect || !this.stageElement) {
311
+ this.mount();
312
+ }
313
+ if (this.cutoutRect && this.stageElement) {
314
+ if (!animate || !this.config.animate) {
315
+ this.cutoutRect.style.transition = "none";
316
+ this.stageElement.style.transition = "none";
317
+ } else {
318
+ this.cutoutRect.style.transition = "";
319
+ this.stageElement.style.transition = "";
320
+ }
321
+ this.cutoutRect.setAttribute("x", String(rect.left));
322
+ this.cutoutRect.setAttribute("y", String(rect.top));
323
+ this.cutoutRect.setAttribute("width", String(Math.max(0, rect.width)));
324
+ this.cutoutRect.setAttribute("height", String(Math.max(0, rect.height)));
325
+ this.cutoutRect.setAttribute("rx", String(rect.radius));
326
+ this.cutoutRect.setAttribute("ry", String(rect.radius));
327
+ this.stageElement.style.left = `${rect.left}px`;
328
+ this.stageElement.style.top = `${rect.top}px`;
329
+ this.stageElement.style.width = `${Math.max(0, rect.width)}px`;
330
+ this.stageElement.style.height = `${Math.max(0, rect.height)}px`;
331
+ this.stageElement.style.borderRadius = `${rect.radius}px`;
332
+ }
333
+ }
334
+ getRootElement() {
335
+ if (!this.rootElement || !this.rootElement.isConnected) {
336
+ this.mount();
337
+ }
338
+ return this.rootElement;
339
+ }
340
+ getStageElement() {
341
+ return this.stageElement;
342
+ }
343
+ destroy() {
344
+ removeElement(this.rootElement);
345
+ this.rootElement = null;
346
+ this.cutoutRect = null;
347
+ this.stageElement = null;
348
+ }
349
+ };
350
+
351
+ // src/dom/Popover.ts
352
+ var Popover = class {
353
+ constructor(callbacks, config = {}) {
354
+ this.element = null;
355
+ this.headerEl = null;
356
+ this.titleEl = null;
357
+ this.closeBtn = null;
358
+ this.contentEl = null;
359
+ this.footerEl = null;
360
+ this.prevBtn = null;
361
+ this.nextBtn = null;
362
+ this.progressEl = null;
363
+ this.arrowEl = null;
364
+ this.callbacks = callbacks;
365
+ this.config = config;
366
+ }
367
+ mount(container) {
368
+ if (this.element && this.element.isConnected) return;
369
+ const popover = document.createElement("div");
370
+ popover.setAttribute("data-webdrive-popover", "");
371
+ popover.className = "webdrive-popover";
372
+ popover.setAttribute("role", "dialog");
373
+ popover.setAttribute("aria-modal", "true");
374
+ popover.setAttribute("aria-labelledby", "webdrive-title");
375
+ popover.setAttribute("aria-describedby", "webdrive-description");
376
+ popover.setAttribute("tabindex", "-1");
377
+ const header = document.createElement("div");
378
+ header.setAttribute("data-webdrive-header", "");
379
+ header.className = "webdrive-header";
380
+ const title = document.createElement("h2");
381
+ title.setAttribute("data-webdrive-title", "");
382
+ title.id = "webdrive-title";
383
+ title.className = "webdrive-title";
384
+ const closeBtn = document.createElement("button");
385
+ closeBtn.setAttribute("data-webdrive-close", "");
386
+ closeBtn.setAttribute("type", "button");
387
+ closeBtn.className = "webdrive-close";
388
+ closeBtn.innerHTML = "&times;";
389
+ closeBtn.setAttribute("aria-label", this.config.closeButtonText || "Close tour");
390
+ closeBtn.addEventListener("click", (e) => {
391
+ e.stopPropagation();
392
+ this.callbacks.onClose();
393
+ });
394
+ header.appendChild(title);
395
+ header.appendChild(closeBtn);
396
+ const content = document.createElement("div");
397
+ content.setAttribute("data-webdrive-content", "");
398
+ content.id = "webdrive-description";
399
+ content.className = "webdrive-content";
400
+ const footer = document.createElement("div");
401
+ footer.setAttribute("data-webdrive-footer", "");
402
+ footer.className = "webdrive-footer";
403
+ const prevBtn = document.createElement("button");
404
+ prevBtn.setAttribute("data-webdrive-prev", "");
405
+ prevBtn.setAttribute("type", "button");
406
+ prevBtn.className = "webdrive-button webdrive-prev";
407
+ prevBtn.addEventListener("click", (e) => {
408
+ e.stopPropagation();
409
+ this.callbacks.onPrev();
410
+ });
411
+ const progress = document.createElement("div");
412
+ progress.setAttribute("data-webdrive-progress", "");
413
+ progress.className = "webdrive-progress";
414
+ const nextBtn = document.createElement("button");
415
+ nextBtn.setAttribute("data-webdrive-next", "");
416
+ nextBtn.setAttribute("type", "button");
417
+ nextBtn.className = "webdrive-button webdrive-next";
418
+ nextBtn.addEventListener("click", (e) => {
419
+ e.stopPropagation();
420
+ this.callbacks.onNext();
421
+ });
422
+ footer.appendChild(prevBtn);
423
+ footer.appendChild(progress);
424
+ footer.appendChild(nextBtn);
425
+ const arrow = document.createElement("div");
426
+ arrow.setAttribute("data-webdrive-arrow", "");
427
+ arrow.className = "webdrive-arrow";
428
+ popover.appendChild(header);
429
+ popover.appendChild(content);
430
+ popover.appendChild(footer);
431
+ popover.appendChild(arrow);
432
+ container.appendChild(popover);
433
+ this.element = popover;
434
+ this.headerEl = header;
435
+ this.titleEl = title;
436
+ this.closeBtn = closeBtn;
437
+ this.contentEl = content;
438
+ this.footerEl = footer;
439
+ this.prevBtn = prevBtn;
440
+ this.nextBtn = nextBtn;
441
+ this.progressEl = progress;
442
+ this.arrowEl = arrow;
443
+ }
444
+ renderStep(step, stepIndex, totalSteps) {
445
+ if (!this.element) return;
446
+ if (this.titleEl) {
447
+ if (step.title) {
448
+ this.titleEl.textContent = step.title;
449
+ this.titleEl.style.display = "";
450
+ } else {
451
+ this.titleEl.textContent = "";
452
+ this.titleEl.style.display = "none";
453
+ }
454
+ }
455
+ const showClose = step.showCloseButton !== void 0 ? step.showCloseButton : this.config.allowClose !== false;
456
+ if (this.closeBtn) {
457
+ this.closeBtn.style.display = showClose ? "" : "none";
458
+ const closeText = step.closeButtonText || this.config.closeButtonText || "Close tour";
459
+ this.closeBtn.setAttribute("aria-label", closeText);
460
+ }
461
+ if (this.headerEl) {
462
+ if (!step.title && !showClose) {
463
+ this.headerEl.style.display = "none";
464
+ } else {
465
+ this.headerEl.style.display = "";
466
+ }
467
+ }
468
+ if (this.contentEl) {
469
+ if (step.content) {
470
+ this.contentEl.innerHTML = step.content;
471
+ } else if (step.description) {
472
+ this.contentEl.textContent = step.description;
473
+ } else {
474
+ this.contentEl.textContent = "";
475
+ }
476
+ }
477
+ const showProgress = this.config.showProgress !== false && totalSteps > 1;
478
+ if (this.progressEl) {
479
+ if (showProgress) {
480
+ this.progressEl.style.display = "";
481
+ if (this.config.renderProgress) {
482
+ this.progressEl.textContent = this.config.renderProgress(
483
+ stepIndex + 1,
484
+ totalSteps
485
+ );
486
+ } else {
487
+ this.progressEl.textContent = `${stepIndex + 1} / ${totalSteps}`;
488
+ }
489
+ } else {
490
+ this.progressEl.style.display = "none";
491
+ }
492
+ }
493
+ const isFirstStep = stepIndex === 0;
494
+ const showPrev = step.showPreviousButton !== void 0 ? step.showPreviousButton : this.config.showButtons !== false;
495
+ if (this.prevBtn) {
496
+ if (!showPrev) {
497
+ this.prevBtn.style.display = "none";
498
+ } else {
499
+ this.prevBtn.style.display = "";
500
+ this.prevBtn.disabled = isFirstStep;
501
+ this.prevBtn.textContent = step.previousButtonText || this.config.previousButtonText || "Previous";
502
+ if (isFirstStep) {
503
+ this.prevBtn.setAttribute("aria-disabled", "true");
504
+ } else {
505
+ this.prevBtn.removeAttribute("aria-disabled");
506
+ }
507
+ }
508
+ }
509
+ const isLastStep = stepIndex === totalSteps - 1;
510
+ const showNext = step.showNextButton !== void 0 ? step.showNextButton : this.config.showButtons !== false;
511
+ if (this.nextBtn) {
512
+ if (!showNext) {
513
+ this.nextBtn.style.display = "none";
514
+ } else {
515
+ this.nextBtn.style.display = "";
516
+ if (isLastStep) {
517
+ this.nextBtn.textContent = step.doneButtonText || this.config.doneButtonText || "Done";
518
+ } else {
519
+ this.nextBtn.textContent = step.nextButtonText || this.config.nextButtonText || "Next";
520
+ }
521
+ }
522
+ }
523
+ if (this.footerEl) {
524
+ const showFooter = showPrev || showNext || showProgress;
525
+ this.footerEl.style.display = showFooter ? "" : "none";
526
+ }
527
+ }
528
+ applyPosition(pos, animate = true) {
529
+ if (!this.element) return;
530
+ if (!animate) {
531
+ this.element.style.transition = "none";
532
+ } else {
533
+ this.element.style.transition = "";
534
+ }
535
+ this.element.style.left = `${pos.left}px`;
536
+ this.element.style.top = `${pos.top}px`;
537
+ this.element.setAttribute("data-placement", pos.placement);
538
+ this.element.setAttribute("data-alignment", pos.alignment);
539
+ if (this.arrowEl) {
540
+ this.arrowEl.setAttribute("data-arrow-placement", pos.arrowPlacement || "top");
541
+ if (pos.arrowLeft !== void 0) {
542
+ this.arrowEl.style.left = `${pos.arrowLeft}px`;
543
+ } else {
544
+ this.arrowEl.style.left = "";
545
+ }
546
+ if (pos.arrowTop !== void 0) {
547
+ this.arrowEl.style.top = `${pos.arrowTop}px`;
548
+ } else {
549
+ this.arrowEl.style.top = "";
550
+ }
551
+ }
552
+ }
553
+ getElement() {
554
+ return this.element;
555
+ }
556
+ getNextButton() {
557
+ return this.nextBtn;
558
+ }
559
+ getDimensions() {
560
+ if (!this.element) {
561
+ return { width: 320, height: 180 };
562
+ }
563
+ const rect = this.element.getBoundingClientRect();
564
+ return {
565
+ width: rect.width || 320,
566
+ height: rect.height || 180
567
+ };
568
+ }
569
+ destroy() {
570
+ removeElement(this.element);
571
+ this.element = null;
572
+ this.headerEl = null;
573
+ this.titleEl = null;
574
+ this.closeBtn = null;
575
+ this.contentEl = null;
576
+ this.footerEl = null;
577
+ this.prevBtn = null;
578
+ this.nextBtn = null;
579
+ this.progressEl = null;
580
+ this.arrowEl = null;
581
+ }
582
+ };
583
+
584
+ // src/dom/Highlight.ts
585
+ var Highlight = class {
586
+ constructor() {
587
+ this.currentElement = null;
588
+ this.padding = 0;
589
+ this.radius = 4;
590
+ }
591
+ setTarget(element, padding = 8, radius = 4) {
592
+ this.currentElement = element;
593
+ this.padding = padding;
594
+ this.radius = radius;
595
+ return this.getRect();
596
+ }
597
+ getRect() {
598
+ if (!this.currentElement || !this.currentElement.isConnected) {
599
+ return {
600
+ top: 0,
601
+ left: 0,
602
+ width: 0,
603
+ height: 0,
604
+ right: 0,
605
+ bottom: 0,
606
+ radius: this.radius
607
+ };
608
+ }
609
+ const box = getElementBoundingBox(this.currentElement, this.padding);
610
+ return {
611
+ ...box,
612
+ radius: this.radius
613
+ };
614
+ }
615
+ getElement() {
616
+ return this.currentElement;
617
+ }
618
+ clear() {
619
+ this.currentElement = null;
620
+ }
621
+ };
622
+
623
+ // src/accessibility/AccessibilityManager.ts
624
+ var AccessibilityManager = class {
625
+ constructor(config = {}) {
626
+ this.previousActiveElement = null;
627
+ this.keydownListener = null;
628
+ this.popoverElement = null;
629
+ this.config = {
630
+ keyboardNavigation: config.keyboardNavigation ?? true,
631
+ closeOnEscape: config.closeOnEscape ?? true
632
+ };
633
+ }
634
+ saveActiveElement() {
635
+ if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) {
636
+ this.previousActiveElement = document.activeElement;
637
+ }
638
+ }
639
+ attach(popover, callbacks) {
640
+ this.popoverElement = popover;
641
+ if (this.keydownListener) {
642
+ this.detach();
643
+ }
644
+ this.keydownListener = (e) => {
645
+ if ((this.config.closeOnEscape !== false || this.config.keyboardNavigation) && (e.key === "Escape" || e.key === "Esc")) {
646
+ e.preventDefault();
647
+ callbacks.onClose();
648
+ return;
649
+ }
650
+ if (this.config.keyboardNavigation) {
651
+ if (e.key === "ArrowRight") {
652
+ e.preventDefault();
653
+ callbacks.onNext();
654
+ return;
655
+ }
656
+ if (e.key === "ArrowLeft") {
657
+ e.preventDefault();
658
+ callbacks.onPrev();
659
+ return;
660
+ }
661
+ }
662
+ if (e.key === "Tab" && this.popoverElement) {
663
+ const focusables = getFocusableElements(this.popoverElement);
664
+ if (focusables.length === 0) {
665
+ e.preventDefault();
666
+ return;
667
+ }
668
+ const first = focusables[0];
669
+ const last = focusables[focusables.length - 1];
670
+ if (e.shiftKey) {
671
+ if (document.activeElement === first) {
672
+ e.preventDefault();
673
+ last?.focus();
674
+ }
675
+ } else {
676
+ if (document.activeElement === last) {
677
+ e.preventDefault();
678
+ first?.focus();
679
+ }
680
+ }
681
+ }
682
+ };
683
+ if (typeof window !== "undefined") {
684
+ window.addEventListener("keydown", this.keydownListener);
685
+ }
686
+ }
687
+ focusPopover(preferredElement) {
688
+ if (preferredElement && typeof preferredElement.focus === "function") {
689
+ preferredElement.focus();
690
+ return;
691
+ }
692
+ if (this.popoverElement) {
693
+ const focusables = getFocusableElements(this.popoverElement);
694
+ if (focusables.length > 0 && focusables[0]) {
695
+ focusables[0].focus();
696
+ } else {
697
+ this.popoverElement.focus();
698
+ }
699
+ }
700
+ }
701
+ restoreFocus() {
702
+ if (this.previousActiveElement && this.previousActiveElement.isConnected && typeof this.previousActiveElement.focus === "function") {
703
+ try {
704
+ this.previousActiveElement.focus();
705
+ } catch {
706
+ }
707
+ }
708
+ this.previousActiveElement = null;
709
+ }
710
+ detach() {
711
+ if (typeof window !== "undefined" && this.keydownListener) {
712
+ window.removeEventListener("keydown", this.keydownListener);
713
+ }
714
+ this.keydownListener = null;
715
+ this.popoverElement = null;
716
+ }
717
+ };
718
+
719
+ // src/positioning/PositionEngine.ts
720
+ function getRawPosition(target, popover, placement, alignment, offset) {
721
+ let top = 0;
722
+ let left = 0;
723
+ switch (placement) {
724
+ case "top":
725
+ top = target.top - popover.height - offset;
726
+ break;
727
+ case "bottom":
728
+ top = target.top + target.height + offset;
729
+ break;
730
+ case "left":
731
+ left = target.left - popover.width - offset;
732
+ break;
733
+ case "right":
734
+ left = target.left + target.width + offset;
735
+ break;
736
+ }
737
+ if (placement === "top" || placement === "bottom") {
738
+ switch (alignment) {
739
+ case "start":
740
+ left = target.left;
741
+ break;
742
+ case "center":
743
+ left = target.left + (target.width - popover.width) / 2;
744
+ break;
745
+ case "end":
746
+ left = target.left + target.width - popover.width;
747
+ break;
748
+ }
749
+ } else {
750
+ switch (alignment) {
751
+ case "start":
752
+ top = target.top;
753
+ break;
754
+ case "center":
755
+ top = target.top + (target.height - popover.height) / 2;
756
+ break;
757
+ case "end":
758
+ top = target.top + target.height - popover.height;
759
+ break;
760
+ }
761
+ }
762
+ return { top, left };
763
+ }
764
+ function fitsInViewport(pos, popover, vw, vh, padding) {
765
+ return pos.left >= padding && pos.top >= padding && pos.left + popover.width <= vw - padding && pos.top + popover.height <= vh - padding;
766
+ }
767
+ function getVisibleArea(pos, popover, vw, vh, padding) {
768
+ const visibleLeft = Math.max(padding, pos.left);
769
+ const visibleTop = Math.max(padding, pos.top);
770
+ const visibleRight = Math.min(vw - padding, pos.left + popover.width);
771
+ const visibleBottom = Math.min(vh - padding, pos.top + popover.height);
772
+ const visibleW = Math.max(0, visibleRight - visibleLeft);
773
+ const visibleH = Math.max(0, visibleBottom - visibleTop);
774
+ return visibleW * visibleH;
775
+ }
776
+ function getFallbackPlacements(preferred) {
777
+ switch (preferred) {
778
+ case "right":
779
+ return ["right", "left", "bottom", "top"];
780
+ case "left":
781
+ return ["left", "right", "bottom", "top"];
782
+ case "bottom":
783
+ return ["bottom", "top", "right", "left"];
784
+ case "top":
785
+ return ["top", "bottom", "right", "left"];
786
+ }
787
+ }
788
+ function calculatePopoverPosition(params) {
789
+ const {
790
+ targetRect,
791
+ popoverRect,
792
+ placement: preferredPlacement = "bottom",
793
+ alignment = "center",
794
+ offset = 12,
795
+ viewportPadding = 12,
796
+ arrowSize = 8
797
+ } = params;
798
+ const vw = params.viewportWidth ?? (typeof window !== "undefined" ? window.innerWidth : 1024);
799
+ const vh = params.viewportHeight ?? (typeof window !== "undefined" ? window.innerHeight : 768);
800
+ const placementsToTest = getFallbackPlacements(preferredPlacement);
801
+ let chosenPlacement = preferredPlacement;
802
+ let chosenPos = getRawPosition(
803
+ targetRect,
804
+ popoverRect,
805
+ preferredPlacement,
806
+ alignment,
807
+ offset
808
+ );
809
+ let foundFit = false;
810
+ for (const p of placementsToTest) {
811
+ const rawPos = getRawPosition(
812
+ targetRect,
813
+ popoverRect,
814
+ p,
815
+ alignment,
816
+ offset
817
+ );
818
+ if (fitsInViewport(rawPos, popoverRect, vw, vh, viewportPadding)) {
819
+ chosenPlacement = p;
820
+ chosenPos = rawPos;
821
+ foundFit = true;
822
+ break;
823
+ }
824
+ }
825
+ if (!foundFit) {
826
+ let bestPlacement = preferredPlacement;
827
+ let bestScore = -1;
828
+ let bestPos = chosenPos;
829
+ for (const p of placementsToTest) {
830
+ const rawPos = getRawPosition(
831
+ targetRect,
832
+ popoverRect,
833
+ p,
834
+ alignment,
835
+ offset
836
+ );
837
+ const score = getVisibleArea(
838
+ rawPos,
839
+ popoverRect,
840
+ vw,
841
+ vh,
842
+ viewportPadding
843
+ );
844
+ if (score > bestScore) {
845
+ bestScore = score;
846
+ bestPlacement = p;
847
+ bestPos = rawPos;
848
+ }
849
+ }
850
+ chosenPlacement = bestPlacement;
851
+ chosenPos = bestPos;
852
+ }
853
+ const minLeft = viewportPadding;
854
+ const maxLeft = Math.max(viewportPadding, vw - popoverRect.width - viewportPadding);
855
+ const minTop = viewportPadding;
856
+ const maxTop = Math.max(viewportPadding, vh - popoverRect.height - viewportPadding);
857
+ const clampedLeft = Math.max(minLeft, Math.min(chosenPos.left, maxLeft));
858
+ const clampedTop = Math.max(minTop, Math.min(chosenPos.top, maxTop));
859
+ const targetCenterX = targetRect.left + targetRect.width / 2;
860
+ const targetCenterY = targetRect.top + targetRect.height / 2;
861
+ let arrowTop;
862
+ let arrowLeft;
863
+ let arrowPlacement = "top";
864
+ const minArrowOffset = arrowSize + 8;
865
+ switch (chosenPlacement) {
866
+ case "top":
867
+ arrowPlacement = "bottom";
868
+ arrowTop = popoverRect.height;
869
+ arrowLeft = Math.max(
870
+ minArrowOffset,
871
+ Math.min(
872
+ targetCenterX - clampedLeft,
873
+ popoverRect.width - minArrowOffset
874
+ )
875
+ );
876
+ break;
877
+ case "bottom":
878
+ arrowPlacement = "top";
879
+ arrowTop = -arrowSize;
880
+ arrowLeft = Math.max(
881
+ minArrowOffset,
882
+ Math.min(
883
+ targetCenterX - clampedLeft,
884
+ popoverRect.width - minArrowOffset
885
+ )
886
+ );
887
+ break;
888
+ case "left":
889
+ arrowPlacement = "right";
890
+ arrowLeft = popoverRect.width;
891
+ arrowTop = Math.max(
892
+ minArrowOffset,
893
+ Math.min(
894
+ targetCenterY - clampedTop,
895
+ popoverRect.height - minArrowOffset
896
+ )
897
+ );
898
+ break;
899
+ case "right":
900
+ arrowPlacement = "left";
901
+ arrowLeft = -arrowSize;
902
+ arrowTop = Math.max(
903
+ minArrowOffset,
904
+ Math.min(
905
+ targetCenterY - clampedTop,
906
+ popoverRect.height - minArrowOffset
907
+ )
908
+ );
909
+ break;
910
+ }
911
+ return {
912
+ top: Math.round(clampedTop),
913
+ left: Math.round(clampedLeft),
914
+ placement: chosenPlacement,
915
+ alignment,
916
+ arrowTop: arrowTop !== void 0 ? Math.round(arrowTop) : void 0,
917
+ arrowLeft: arrowLeft !== void 0 ? Math.round(arrowLeft) : void 0,
918
+ arrowPlacement
919
+ };
920
+ }
921
+
922
+ // src/core/TourController.ts
923
+ var TourController = class {
924
+ constructor(options, emitter, storage) {
925
+ this.currentStepIndex = -1;
926
+ this.tourActive = false;
927
+ this.destroyed = false;
928
+ this.resizeListener = null;
929
+ this.scrollListener = null;
930
+ this.rafId = null;
931
+ this.waitObserver = null;
932
+ this.waitTimeoutId = null;
933
+ this.options = {
934
+ animate: true,
935
+ smoothScroll: true,
936
+ overlay: true,
937
+ overlayOpacity: 0.6,
938
+ zIndex: 1e5,
939
+ stagePadding: 8,
940
+ stageRadius: 6,
941
+ keyboardNavigation: true,
942
+ closeOnEscape: true,
943
+ showButtons: true,
944
+ showProgress: true,
945
+ allowClose: true,
946
+ missingElementBehavior: "skip",
947
+ missingElementWaitTimeout: 3e3,
948
+ ...options
949
+ };
950
+ this.emitter = emitter;
951
+ this.storage = storage;
952
+ this.highlight = new Highlight();
953
+ this.overlay = new Overlay({
954
+ enabled: this.options.overlay !== false,
955
+ opacity: this.options.overlayOpacity,
956
+ color: this.options.overlayColor,
957
+ zIndex: this.options.zIndex,
958
+ animate: this.options.animate,
959
+ allowClose: this.options.allowClose,
960
+ onOverlayClick: () => this.stop()
961
+ });
962
+ this.popover = new Popover(
963
+ {
964
+ onPrev: () => this.previous(),
965
+ onNext: () => this.next(),
966
+ onClose: () => this.stop()
967
+ },
968
+ {
969
+ allowClose: this.options.allowClose,
970
+ showProgress: this.options.showProgress,
971
+ showButtons: this.options.showButtons,
972
+ nextButtonText: this.options.nextButtonText,
973
+ previousButtonText: this.options.previousButtonText,
974
+ doneButtonText: this.options.doneButtonText,
975
+ closeButtonText: this.options.closeButtonText,
976
+ renderProgress: this.options.renderProgress
977
+ }
978
+ );
979
+ this.accessibility = new AccessibilityManager({
980
+ keyboardNavigation: this.options.keyboardNavigation,
981
+ closeOnEscape: this.options.closeOnEscape
982
+ });
983
+ }
984
+ async start(startIndex = 0) {
985
+ if (this.destroyed) {
986
+ console.warn("[WebDrive] Cannot start a destroyed tour instance.");
987
+ return;
988
+ }
989
+ if (this.tourActive) {
990
+ return;
991
+ }
992
+ if (!this.options.steps || this.options.steps.length === 0) {
993
+ console.warn("[WebDrive] Cannot start tour without steps.");
994
+ return;
995
+ }
996
+ if (this.options.remember && this.options.id) {
997
+ const alreadyDone = await this.storage.isCompleted(this.options.id);
998
+ if (alreadyDone) {
999
+ return;
1000
+ }
1001
+ }
1002
+ this.tourActive = true;
1003
+ this.accessibility.saveActiveElement();
1004
+ this.overlay.mount();
1005
+ const rootEl = this.overlay.getRootElement();
1006
+ if (rootEl) {
1007
+ this.popover.mount(rootEl);
1008
+ }
1009
+ const popoverEl = this.popover.getElement();
1010
+ if (popoverEl) {
1011
+ this.accessibility.attach(popoverEl, {
1012
+ onNext: () => this.next(),
1013
+ onPrev: () => this.previous(),
1014
+ onClose: () => this.stop()
1015
+ });
1016
+ }
1017
+ this.setupViewportListeners();
1018
+ this.emitter.emit("start");
1019
+ this.options.onStart?.();
1020
+ await this.goTo(startIndex);
1021
+ }
1022
+ async stop() {
1023
+ if (!this.tourActive) return;
1024
+ this.cleanupWaiting();
1025
+ await this.triggerStepLeave();
1026
+ this.tourActive = false;
1027
+ this.currentStepIndex = -1;
1028
+ this.highlight.clear();
1029
+ this.removeViewportListeners();
1030
+ this.accessibility.detach();
1031
+ this.accessibility.restoreFocus();
1032
+ this.overlay.destroy();
1033
+ this.popover.destroy();
1034
+ this.emitter.emit("close");
1035
+ this.options.onClose?.();
1036
+ }
1037
+ async complete() {
1038
+ if (!this.tourActive) return;
1039
+ this.cleanupWaiting();
1040
+ await this.triggerStepLeave();
1041
+ if (this.options.remember && this.options.id) {
1042
+ await this.storage.markCompleted(this.options.id);
1043
+ }
1044
+ this.tourActive = false;
1045
+ this.currentStepIndex = -1;
1046
+ this.highlight.clear();
1047
+ this.removeViewportListeners();
1048
+ this.accessibility.detach();
1049
+ this.accessibility.restoreFocus();
1050
+ this.overlay.destroy();
1051
+ this.popover.destroy();
1052
+ this.emitter.emit("complete");
1053
+ this.options.onComplete?.();
1054
+ }
1055
+ async next() {
1056
+ if (!this.tourActive) return;
1057
+ const nextIdx = this.currentStepIndex + 1;
1058
+ if (nextIdx >= this.options.steps.length) {
1059
+ await this.complete();
1060
+ } else {
1061
+ await this.goTo(nextIdx, "forward");
1062
+ }
1063
+ }
1064
+ async previous() {
1065
+ if (!this.tourActive) return;
1066
+ const prevIdx = this.currentStepIndex - 1;
1067
+ if (prevIdx >= 0) {
1068
+ await this.goTo(prevIdx, "backward");
1069
+ }
1070
+ }
1071
+ async goTo(index, direction = "forward") {
1072
+ if (!this.tourActive || this.destroyed) return;
1073
+ if (index < 0 || index >= this.options.steps.length) {
1074
+ return;
1075
+ }
1076
+ this.cleanupWaiting();
1077
+ const step = this.options.steps[index];
1078
+ if (!step) return;
1079
+ let targetEl = resolveElement(step.element);
1080
+ if (!targetEl) {
1081
+ const behavior = this.options.missingElementBehavior ?? "skip";
1082
+ if (behavior === "stop") {
1083
+ console.warn(`[WebDrive] Stopping tour because element "${String(step.element)}" was not found.`);
1084
+ await this.stop();
1085
+ return;
1086
+ }
1087
+ if (behavior === "wait") {
1088
+ targetEl = await this.waitForElement(
1089
+ step.element,
1090
+ this.options.missingElementWaitTimeout ?? 3e3
1091
+ );
1092
+ if (!targetEl) {
1093
+ console.warn(`[WebDrive] Timeout waiting for element "${String(step.element)}". Skipping.`);
1094
+ await this.advanceFromMissing(index, direction);
1095
+ return;
1096
+ }
1097
+ } else {
1098
+ console.warn(`[WebDrive] Skipping step ${index} because element "${String(step.element)}" was not found.`);
1099
+ await this.advanceFromMissing(index, direction);
1100
+ return;
1101
+ }
1102
+ }
1103
+ await this.triggerStepLeave();
1104
+ this.currentStepIndex = index;
1105
+ if (this.options.smoothScroll !== false) {
1106
+ scrollIntoViewSafely(targetEl, this.options.animate !== false);
1107
+ }
1108
+ const padding = step.padding ?? this.options.stagePadding ?? 8;
1109
+ const radius = this.options.stageRadius ?? 6;
1110
+ const highlightRect = this.highlight.setTarget(targetEl, padding, radius);
1111
+ this.overlay.update(highlightRect, this.options.animate !== false);
1112
+ this.popover.renderStep(step, index, this.options.steps.length);
1113
+ this.updatePosition();
1114
+ const nextBtn = this.popover.getNextButton();
1115
+ this.accessibility.focusPopover(nextBtn);
1116
+ try {
1117
+ await step.onEnter?.();
1118
+ } catch (error) {
1119
+ console.error(`[WebDrive] Error in step ${index} onEnter:`, error);
1120
+ }
1121
+ this.emitter.emit("stepChange", { step, index });
1122
+ this.options.onStepChange?.(step, index);
1123
+ }
1124
+ refresh() {
1125
+ if (!this.tourActive || this.currentStepIndex === -1) return;
1126
+ if (this.rafId !== null && typeof cancelAnimationFrame !== "undefined") {
1127
+ cancelAnimationFrame(this.rafId);
1128
+ }
1129
+ if (typeof requestAnimationFrame !== "undefined") {
1130
+ this.rafId = requestAnimationFrame(() => {
1131
+ this.syncUI();
1132
+ });
1133
+ } else {
1134
+ this.syncUI();
1135
+ }
1136
+ }
1137
+ destroy() {
1138
+ if (this.destroyed) return;
1139
+ this.destroyed = true;
1140
+ this.cleanupWaiting();
1141
+ this.removeViewportListeners();
1142
+ if (this.tourActive) {
1143
+ this.tourActive = false;
1144
+ this.accessibility.detach();
1145
+ this.accessibility.restoreFocus();
1146
+ this.overlay.destroy();
1147
+ this.popover.destroy();
1148
+ }
1149
+ this.emitter.emit("destroy");
1150
+ this.options.onDestroy?.();
1151
+ this.emitter.removeAllListeners();
1152
+ }
1153
+ isActive() {
1154
+ return this.tourActive;
1155
+ }
1156
+ getCurrentStep() {
1157
+ if (!this.tourActive || this.currentStepIndex === -1) {
1158
+ return null;
1159
+ }
1160
+ return this.options.steps[this.currentStepIndex] ?? null;
1161
+ }
1162
+ getCurrentStepIndex() {
1163
+ return this.currentStepIndex;
1164
+ }
1165
+ syncUI() {
1166
+ if (!this.tourActive || this.currentStepIndex === -1) return;
1167
+ const targetEl = this.highlight.getElement();
1168
+ if (!targetEl || !targetEl.isConnected) {
1169
+ return;
1170
+ }
1171
+ const highlightRect = this.highlight.getRect();
1172
+ this.overlay.update(highlightRect, false);
1173
+ this.updatePosition(false);
1174
+ }
1175
+ updatePosition(animate = true) {
1176
+ const step = this.getCurrentStep();
1177
+ if (!step) return;
1178
+ const highlightRect = this.highlight.getRect();
1179
+ const popoverDim = this.popover.getDimensions();
1180
+ const positionResult = calculatePopoverPosition({
1181
+ targetRect: highlightRect,
1182
+ popoverRect: popoverDim,
1183
+ placement: step.position ?? "bottom",
1184
+ alignment: step.align ?? "center",
1185
+ offset: step.offset ?? 12
1186
+ });
1187
+ this.popover.applyPosition(positionResult, animate && this.options.animate !== false);
1188
+ }
1189
+ async triggerStepLeave() {
1190
+ if (this.currentStepIndex >= 0 && this.currentStepIndex < this.options.steps.length) {
1191
+ const prevStep = this.options.steps[this.currentStepIndex];
1192
+ if (prevStep?.onLeave) {
1193
+ try {
1194
+ await prevStep.onLeave();
1195
+ } catch (error) {
1196
+ console.error(`[WebDrive] Error in step ${this.currentStepIndex} onLeave:`, error);
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ async advanceFromMissing(currentIndex, direction) {
1202
+ if (direction === "forward") {
1203
+ const nextIdx = currentIndex + 1;
1204
+ if (nextIdx >= this.options.steps.length) {
1205
+ await this.complete();
1206
+ } else {
1207
+ await this.goTo(nextIdx, "forward");
1208
+ }
1209
+ } else {
1210
+ const prevIdx = currentIndex - 1;
1211
+ if (prevIdx < 0) {
1212
+ await this.stop();
1213
+ } else {
1214
+ await this.goTo(prevIdx, "backward");
1215
+ }
1216
+ }
1217
+ }
1218
+ waitForElement(target, timeoutMs) {
1219
+ return new Promise((resolve) => {
1220
+ const existing = resolveElement(target);
1221
+ if (existing) {
1222
+ resolve(existing);
1223
+ return;
1224
+ }
1225
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") {
1226
+ resolve(null);
1227
+ return;
1228
+ }
1229
+ this.waitObserver = new MutationObserver(() => {
1230
+ const found = resolveElement(target);
1231
+ if (found) {
1232
+ this.cleanupWaiting();
1233
+ resolve(found);
1234
+ }
1235
+ });
1236
+ this.waitObserver.observe(document.body, {
1237
+ childList: true,
1238
+ subtree: true
1239
+ });
1240
+ this.waitTimeoutId = setTimeout(() => {
1241
+ this.cleanupWaiting();
1242
+ resolve(resolveElement(target));
1243
+ }, timeoutMs);
1244
+ });
1245
+ }
1246
+ cleanupWaiting() {
1247
+ if (this.waitObserver) {
1248
+ this.waitObserver.disconnect();
1249
+ this.waitObserver = null;
1250
+ }
1251
+ if (this.waitTimeoutId !== null) {
1252
+ clearTimeout(this.waitTimeoutId);
1253
+ this.waitTimeoutId = null;
1254
+ }
1255
+ }
1256
+ setupViewportListeners() {
1257
+ if (typeof window === "undefined") return;
1258
+ this.resizeListener = () => this.refresh();
1259
+ this.scrollListener = () => this.refresh();
1260
+ window.addEventListener("resize", this.resizeListener, { passive: true });
1261
+ window.addEventListener("scroll", this.scrollListener, { passive: true, capture: true });
1262
+ }
1263
+ removeViewportListeners() {
1264
+ if (typeof window === "undefined") return;
1265
+ if (this.resizeListener) {
1266
+ window.removeEventListener("resize", this.resizeListener);
1267
+ this.resizeListener = null;
1268
+ }
1269
+ if (this.scrollListener) {
1270
+ window.removeEventListener("scroll", this.scrollListener, true);
1271
+ this.scrollListener = null;
1272
+ }
1273
+ if (this.rafId !== null && typeof cancelAnimationFrame !== "undefined") {
1274
+ cancelAnimationFrame(this.rafId);
1275
+ this.rafId = null;
1276
+ }
1277
+ }
1278
+ };
1279
+
1280
+ // src/core/WebDrive.ts
1281
+ var WebDrive = class {
1282
+ constructor(options) {
1283
+ this.controller = null;
1284
+ this.options = { ...options };
1285
+ this.emitter = new EventEmitter();
1286
+ this.storage = new StorageManager(this.options.storage);
1287
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
1288
+ this.controller = new TourController(
1289
+ this.options,
1290
+ this.emitter,
1291
+ this.storage
1292
+ );
1293
+ if (this.options.autoStart) {
1294
+ setTimeout(() => {
1295
+ this.start();
1296
+ }, 0);
1297
+ }
1298
+ }
1299
+ }
1300
+ ensureController() {
1301
+ if (typeof window === "undefined" || typeof document === "undefined") {
1302
+ return null;
1303
+ }
1304
+ if (!this.controller) {
1305
+ this.controller = new TourController(
1306
+ this.options,
1307
+ this.emitter,
1308
+ this.storage
1309
+ );
1310
+ }
1311
+ return this.controller;
1312
+ }
1313
+ async start(startIndex = 0) {
1314
+ const ctrl = this.ensureController();
1315
+ if (ctrl) {
1316
+ await ctrl.start(startIndex);
1317
+ }
1318
+ }
1319
+ async stop() {
1320
+ const ctrl = this.ensureController();
1321
+ if (ctrl) {
1322
+ await ctrl.stop();
1323
+ }
1324
+ }
1325
+ async next() {
1326
+ const ctrl = this.ensureController();
1327
+ if (ctrl) {
1328
+ await ctrl.next();
1329
+ }
1330
+ }
1331
+ async previous() {
1332
+ const ctrl = this.ensureController();
1333
+ if (ctrl) {
1334
+ await ctrl.previous();
1335
+ }
1336
+ }
1337
+ async goTo(index) {
1338
+ const ctrl = this.ensureController();
1339
+ if (ctrl) {
1340
+ await ctrl.goTo(index);
1341
+ }
1342
+ }
1343
+ refresh() {
1344
+ const ctrl = this.ensureController();
1345
+ if (ctrl) {
1346
+ ctrl.refresh();
1347
+ }
1348
+ }
1349
+ destroy() {
1350
+ if (this.controller) {
1351
+ this.controller.destroy();
1352
+ this.controller = null;
1353
+ }
1354
+ }
1355
+ isActive() {
1356
+ return this.controller ? this.controller.isActive() : false;
1357
+ }
1358
+ getCurrentStep() {
1359
+ return this.controller ? this.controller.getCurrentStep() : null;
1360
+ }
1361
+ getCurrentStepIndex() {
1362
+ return this.controller ? this.controller.getCurrentStepIndex() : -1;
1363
+ }
1364
+ async hasCompleted() {
1365
+ if (!this.options.id) return false;
1366
+ return this.storage.isCompleted(this.options.id);
1367
+ }
1368
+ async reset() {
1369
+ if (!this.options.id) return;
1370
+ await this.storage.reset(this.options.id);
1371
+ }
1372
+ async resetAll() {
1373
+ await this.storage.resetAll();
1374
+ }
1375
+ on(event, callback) {
1376
+ this.emitter.on(event, callback);
1377
+ return this;
1378
+ }
1379
+ off(event, callback) {
1380
+ this.emitter.off(event, callback);
1381
+ return this;
1382
+ }
1383
+ };
1384
+
1385
+ // src/index.ts
1386
+ var index_default = WebDrive;
1387
+ export {
1388
+ EventEmitter,
1389
+ StorageManager,
1390
+ TourController,
1391
+ WebDrive,
1392
+ calculatePopoverPosition,
1393
+ index_default as default
1394
+ };
1395
+ //# sourceMappingURL=index.js.map