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