tour-guide-live 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,817 @@
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
+ TourProvider: () => TourProvider,
24
+ TourTooltip: () => TourTooltip,
25
+ useTour: () => useTour
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/context.tsx
30
+ var import_react = require("react");
31
+ var import_jsx_runtime = require("react/jsx-runtime");
32
+ function storageKey(tourId) {
33
+ return `tour_progress_${tourId}`;
34
+ }
35
+ function loadProgress(tourId) {
36
+ if (typeof window === "undefined") return null;
37
+ try {
38
+ const raw = localStorage.getItem(storageKey(tourId));
39
+ return raw ? JSON.parse(raw) : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ function saveProgress(tourId, progress) {
45
+ if (typeof window === "undefined") return;
46
+ try {
47
+ localStorage.setItem(storageKey(tourId), JSON.stringify(progress));
48
+ } catch {
49
+ }
50
+ }
51
+ function reducer(state, action) {
52
+ switch (action.type) {
53
+ case "SET_TOURS":
54
+ return { ...state, tours: action.tours, resolved: true };
55
+ case "START_TOUR":
56
+ return {
57
+ ...state,
58
+ activeTourId: action.tourId,
59
+ currentStepIndex: action.resumeIndex,
60
+ completedSteps: action.completedSteps
61
+ };
62
+ case "END_TOUR":
63
+ return {
64
+ ...state,
65
+ activeTourId: null,
66
+ currentStepIndex: 0,
67
+ completedSteps: []
68
+ };
69
+ case "GO_TO_STEP":
70
+ return { ...state, currentStepIndex: action.index };
71
+ case "COMPLETE_STEP": {
72
+ const tour = state.tours.find((t) => t.id === state.activeTourId);
73
+ if (!tour) return state;
74
+ const newCompleted = state.completedSteps.includes(state.currentStepIndex) ? state.completedSteps : [...state.completedSteps, state.currentStepIndex];
75
+ const isLastStep = state.currentStepIndex >= tour.steps.length - 1;
76
+ if (isLastStep) {
77
+ return {
78
+ ...state,
79
+ completedSteps: newCompleted
80
+ // Tour will be ended by the effect that detects completion
81
+ };
82
+ }
83
+ return {
84
+ ...state,
85
+ completedSteps: newCompleted,
86
+ currentStepIndex: state.currentStepIndex + 1
87
+ };
88
+ }
89
+ case "SKIP_TOUR":
90
+ return {
91
+ ...state,
92
+ activeTourId: null,
93
+ currentStepIndex: 0,
94
+ completedSteps: []
95
+ };
96
+ default:
97
+ return state;
98
+ }
99
+ }
100
+ var initialState = {
101
+ tours: [],
102
+ activeTourId: null,
103
+ currentStepIndex: 0,
104
+ completedSteps: [],
105
+ resolved: false
106
+ };
107
+ var TourContext = (0, import_react.createContext)(null);
108
+ function TourProvider({
109
+ apiKey,
110
+ userId,
111
+ userAttributes,
112
+ apiBaseUrl = "",
113
+ children
114
+ }) {
115
+ const [state, dispatch] = (0, import_react.useReducer)(reducer, initialState);
116
+ const baseUrl = apiBaseUrl.replace(/\/$/, "");
117
+ const autoStarted = (0, import_react.useRef)(false);
118
+ (0, import_react.useEffect)(() => {
119
+ if (typeof window === "undefined") return;
120
+ let cancelled = false;
121
+ async function resolve() {
122
+ try {
123
+ const currentUrl = window.location.pathname;
124
+ const res = await fetch(`${baseUrl}/api/tours/resolve`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({ apiKey, userId, userAttributes, currentUrl })
128
+ });
129
+ if (!res.ok) {
130
+ console.warn("[tour-sdk] Failed to resolve tours:", res.status);
131
+ return;
132
+ }
133
+ const data = await res.json();
134
+ if (!cancelled) {
135
+ dispatch({ type: "SET_TOURS", tours: data.tours ?? [] });
136
+ }
137
+ } catch (err) {
138
+ console.warn("[tour-sdk] Error resolving tours:", err);
139
+ }
140
+ }
141
+ resolve();
142
+ return () => {
143
+ cancelled = true;
144
+ };
145
+ }, [baseUrl, apiKey, userId]);
146
+ (0, import_react.useEffect)(() => {
147
+ if (!state.resolved || autoStarted.current || state.tours.length === 0) return;
148
+ for (const tour of state.tours) {
149
+ const progress = loadProgress(tour.id);
150
+ if (progress?.completedAt || progress?.skipped) continue;
151
+ autoStarted.current = true;
152
+ const completed = progress?.completedSteps ?? [];
153
+ let resumeIndex = 0;
154
+ for (let i = 0; i < tour.steps.length; i++) {
155
+ if (!completed.includes(i)) {
156
+ resumeIndex = i;
157
+ break;
158
+ }
159
+ }
160
+ dispatch({
161
+ type: "START_TOUR",
162
+ tourId: tour.id,
163
+ resumeIndex,
164
+ completedSteps: completed
165
+ });
166
+ break;
167
+ }
168
+ }, [state.resolved, state.tours]);
169
+ const activeTour = (0, import_react.useMemo)(
170
+ () => state.tours.find((t) => t.id === state.activeTourId) ?? null,
171
+ [state.tours, state.activeTourId]
172
+ );
173
+ (0, import_react.useEffect)(() => {
174
+ if (!state.activeTourId || !activeTour) return;
175
+ const isComplete = activeTour.steps.length > 0 && state.completedSteps.length >= activeTour.steps.length;
176
+ saveProgress(state.activeTourId, {
177
+ completedSteps: state.completedSteps,
178
+ completedAt: isComplete ? (/* @__PURE__ */ new Date()).toISOString() : null,
179
+ skipped: false
180
+ });
181
+ if (isComplete) {
182
+ syncProgress(state.activeTourId, state.completedSteps, false, (/* @__PURE__ */ new Date()).toISOString());
183
+ dispatch({ type: "END_TOUR" });
184
+ }
185
+ }, [state.activeTourId, state.completedSteps, activeTour]);
186
+ const syncProgress = (0, import_react.useCallback)(
187
+ async (tourId, completedSteps, skipped, completedAt) => {
188
+ try {
189
+ await fetch(`${baseUrl}/api/tours/progress`, {
190
+ method: "POST",
191
+ headers: { "Content-Type": "application/json" },
192
+ body: JSON.stringify({
193
+ apiKey,
194
+ userId,
195
+ tourId,
196
+ completedSteps,
197
+ skipped,
198
+ completedAt
199
+ })
200
+ });
201
+ } catch (err) {
202
+ console.warn("[tour-sdk] Failed to sync progress:", err);
203
+ }
204
+ },
205
+ [baseUrl, apiKey, userId]
206
+ );
207
+ const startTour = (0, import_react.useCallback)(
208
+ (tourId) => {
209
+ const tour = state.tours.find((t) => t.id === tourId);
210
+ if (!tour) {
211
+ console.warn(`[tour-sdk] Tour "${tourId}" not found`);
212
+ return;
213
+ }
214
+ const progress = loadProgress(tourId);
215
+ const completed = progress?.completedSteps ?? [];
216
+ let resumeIndex = 0;
217
+ for (let i = 0; i < tour.steps.length; i++) {
218
+ if (!completed.includes(i)) {
219
+ resumeIndex = i;
220
+ break;
221
+ }
222
+ }
223
+ dispatch({ type: "START_TOUR", tourId, resumeIndex, completedSteps: completed });
224
+ },
225
+ [state.tours]
226
+ );
227
+ const endTour = (0, import_react.useCallback)(() => {
228
+ dispatch({ type: "END_TOUR" });
229
+ }, []);
230
+ const goToStep = (0, import_react.useCallback)((index) => {
231
+ dispatch({ type: "GO_TO_STEP", index });
232
+ }, []);
233
+ const completeStep = (0, import_react.useCallback)(() => {
234
+ dispatch({ type: "COMPLETE_STEP" });
235
+ }, []);
236
+ const skipTour = (0, import_react.useCallback)(() => {
237
+ if (state.activeTourId) {
238
+ saveProgress(state.activeTourId, {
239
+ completedSteps: state.completedSteps,
240
+ completedAt: null,
241
+ skipped: true
242
+ });
243
+ syncProgress(state.activeTourId, state.completedSteps, true, null);
244
+ }
245
+ dispatch({ type: "SKIP_TOUR" });
246
+ }, [state.activeTourId, state.completedSteps, syncProgress]);
247
+ const currentStep = activeTour ? activeTour.steps[state.currentStepIndex] ?? null : null;
248
+ const contextValue = (0, import_react.useMemo)(
249
+ () => ({
250
+ startTour,
251
+ endTour,
252
+ goToStep,
253
+ completeStep,
254
+ skipTour,
255
+ currentStep,
256
+ currentStepIndex: state.currentStepIndex,
257
+ activeTourId: state.activeTourId,
258
+ activeTour,
259
+ isActive: state.activeTourId !== null && currentStep !== null
260
+ }),
261
+ [
262
+ startTour,
263
+ endTour,
264
+ goToStep,
265
+ completeStep,
266
+ skipTour,
267
+ currentStep,
268
+ state.currentStepIndex,
269
+ state.activeTourId,
270
+ activeTour
271
+ ]
272
+ );
273
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TourContext.Provider, { value: contextValue, children });
274
+ }
275
+ function useTour() {
276
+ const ctx = (0, import_react.useContext)(TourContext);
277
+ if (!ctx) {
278
+ throw new Error("useTour must be used within a TourProvider");
279
+ }
280
+ return ctx;
281
+ }
282
+
283
+ // src/TourTooltip.tsx
284
+ var import_react5 = require("react");
285
+ var import_react_dom = require("react-dom");
286
+
287
+ // src/hooks/useElementDetection.ts
288
+ var import_react2 = require("react");
289
+ var TIMEOUT_MS = 5e3;
290
+ function useElementDetection(target) {
291
+ const [element, setElement] = (0, import_react2.useState)(null);
292
+ const prevTarget = (0, import_react2.useRef)(target);
293
+ if (prevTarget.current !== target) {
294
+ prevTarget.current = target;
295
+ }
296
+ (0, import_react2.useEffect)(() => {
297
+ if (typeof window === "undefined" || !target) {
298
+ setElement(null);
299
+ return;
300
+ }
301
+ const selector = target.startsWith("[") || target.startsWith("#") || target.startsWith(".") ? target : `[data-tour="${target}"]`;
302
+ const found = document.querySelector(selector);
303
+ if (found) {
304
+ setElement(found);
305
+ return;
306
+ }
307
+ let timedOut = false;
308
+ const observer = new MutationObserver(() => {
309
+ const el = document.querySelector(selector);
310
+ if (el) {
311
+ observer.disconnect();
312
+ if (!timedOut) setElement(el);
313
+ }
314
+ });
315
+ observer.observe(document.body, { childList: true, subtree: true });
316
+ const timer = setTimeout(() => {
317
+ timedOut = true;
318
+ observer.disconnect();
319
+ console.warn(
320
+ `[tour-sdk] Element "${target}" not found after ${TIMEOUT_MS}ms \u2014 skipping step`
321
+ );
322
+ setElement(null);
323
+ }, TIMEOUT_MS);
324
+ return () => {
325
+ timedOut = true;
326
+ observer.disconnect();
327
+ clearTimeout(timer);
328
+ };
329
+ }, [target]);
330
+ return element;
331
+ }
332
+
333
+ // src/hooks/useTooltipPosition.ts
334
+ var import_react3 = require("react");
335
+ var ARROW_SIZE = 8;
336
+ var TOOLTIP_GAP = 12;
337
+ var VIEWPORT_PADDING = 8;
338
+ function useTooltipPosition(targetEl, tooltipEl, preferredPlacement) {
339
+ const [position, setPosition] = (0, import_react3.useState)({
340
+ top: 0,
341
+ left: 0,
342
+ actualPlacement: preferredPlacement,
343
+ arrowTop: 0,
344
+ arrowLeft: 0,
345
+ targetRect: null
346
+ });
347
+ const calculate = (0, import_react3.useCallback)(() => {
348
+ if (typeof window === "undefined" || !targetEl || !tooltipEl) return;
349
+ const targetRect = targetEl.getBoundingClientRect();
350
+ const tooltipRect = tooltipEl.getBoundingClientRect();
351
+ const vw = window.innerWidth;
352
+ const vh = window.innerHeight;
353
+ const tooltipW = tooltipRect.width;
354
+ const tooltipH = tooltipRect.height;
355
+ function getPos(p) {
356
+ let top2 = 0;
357
+ let left2 = 0;
358
+ switch (p) {
359
+ case "bottom":
360
+ top2 = targetRect.bottom + TOOLTIP_GAP;
361
+ left2 = targetRect.left + targetRect.width / 2 - tooltipW / 2;
362
+ break;
363
+ case "top":
364
+ top2 = targetRect.top - tooltipH - TOOLTIP_GAP;
365
+ left2 = targetRect.left + targetRect.width / 2 - tooltipW / 2;
366
+ break;
367
+ case "right":
368
+ top2 = targetRect.top + targetRect.height / 2 - tooltipH / 2;
369
+ left2 = targetRect.right + TOOLTIP_GAP;
370
+ break;
371
+ case "left":
372
+ top2 = targetRect.top + targetRect.height / 2 - tooltipH / 2;
373
+ left2 = targetRect.left - tooltipW - TOOLTIP_GAP;
374
+ break;
375
+ }
376
+ return { top: top2, left: left2 };
377
+ }
378
+ function fits(p) {
379
+ const { top: top2, left: left2 } = getPos(p);
380
+ return top2 >= VIEWPORT_PADDING && left2 >= VIEWPORT_PADDING && top2 + tooltipH <= vh - VIEWPORT_PADDING && left2 + tooltipW <= vw - VIEWPORT_PADDING;
381
+ }
382
+ const opposite = {
383
+ top: "bottom",
384
+ bottom: "top",
385
+ left: "right",
386
+ right: "left"
387
+ };
388
+ const fallbackOrder = ["bottom", "top", "right", "left"];
389
+ let actual = preferredPlacement;
390
+ if (!fits(preferredPlacement)) {
391
+ if (fits(opposite[preferredPlacement])) {
392
+ actual = opposite[preferredPlacement];
393
+ } else {
394
+ actual = fallbackOrder.find(fits) ?? preferredPlacement;
395
+ }
396
+ }
397
+ let { top, left } = getPos(actual);
398
+ left = Math.max(VIEWPORT_PADDING, Math.min(left, vw - tooltipW - VIEWPORT_PADDING));
399
+ top = Math.max(VIEWPORT_PADDING, Math.min(top, vh - tooltipH - VIEWPORT_PADDING));
400
+ let arrowTop = 0;
401
+ let arrowLeft = 0;
402
+ const targetCenterX = targetRect.left + targetRect.width / 2;
403
+ const targetCenterY = targetRect.top + targetRect.height / 2;
404
+ switch (actual) {
405
+ case "bottom":
406
+ arrowTop = -ARROW_SIZE;
407
+ arrowLeft = Math.max(12, Math.min(targetCenterX - left, tooltipW - 12));
408
+ break;
409
+ case "top":
410
+ arrowTop = tooltipH;
411
+ arrowLeft = Math.max(12, Math.min(targetCenterX - left, tooltipW - 12));
412
+ break;
413
+ case "right":
414
+ arrowLeft = -ARROW_SIZE;
415
+ arrowTop = Math.max(12, Math.min(targetCenterY - top, tooltipH - 12));
416
+ break;
417
+ case "left":
418
+ arrowLeft = tooltipW;
419
+ arrowTop = Math.max(12, Math.min(targetCenterY - top, tooltipH - 12));
420
+ break;
421
+ }
422
+ setPosition({
423
+ top,
424
+ left,
425
+ actualPlacement: actual,
426
+ arrowTop,
427
+ arrowLeft,
428
+ targetRect
429
+ });
430
+ }, [targetEl, tooltipEl, preferredPlacement]);
431
+ (0, import_react3.useEffect)(() => {
432
+ if (typeof window === "undefined" || !targetEl || !tooltipEl) return;
433
+ calculate();
434
+ const onScroll = () => calculate();
435
+ window.addEventListener("scroll", onScroll, true);
436
+ window.addEventListener("resize", onScroll);
437
+ let resizeObserver;
438
+ if (typeof ResizeObserver !== "undefined") {
439
+ resizeObserver = new ResizeObserver(() => calculate());
440
+ resizeObserver.observe(targetEl);
441
+ resizeObserver.observe(tooltipEl);
442
+ }
443
+ return () => {
444
+ window.removeEventListener("scroll", onScroll, true);
445
+ window.removeEventListener("resize", onScroll);
446
+ resizeObserver?.disconnect();
447
+ };
448
+ }, [targetEl, tooltipEl, calculate]);
449
+ return position;
450
+ }
451
+
452
+ // src/hooks/useStepTrigger.ts
453
+ var import_react4 = require("react");
454
+ function useStepTrigger(step, targetEl, onComplete) {
455
+ (0, import_react4.useEffect)(() => {
456
+ if (!step || step.completionTrigger !== "element-click" || !targetEl) return;
457
+ const handler = () => onComplete();
458
+ targetEl.addEventListener("click", handler, { once: true });
459
+ return () => targetEl.removeEventListener("click", handler);
460
+ }, [step, targetEl, onComplete]);
461
+ (0, import_react4.useEffect)(() => {
462
+ if (typeof window === "undefined" || !step || step.completionTrigger !== "url-change" || !step.urlTrigger)
463
+ return;
464
+ const pattern = step.urlTrigger;
465
+ function matchesUrl() {
466
+ const path = window.location.pathname;
467
+ const regex = new RegExp(
468
+ "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
469
+ );
470
+ return regex.test(path);
471
+ }
472
+ if (matchesUrl()) {
473
+ onComplete();
474
+ return;
475
+ }
476
+ const onUrlChange = () => {
477
+ if (matchesUrl()) {
478
+ onComplete();
479
+ }
480
+ };
481
+ window.addEventListener("popstate", onUrlChange);
482
+ const originalPush = history.pushState.bind(history);
483
+ const originalReplace = history.replaceState.bind(history);
484
+ history.pushState = function(...args) {
485
+ originalPush(...args);
486
+ onUrlChange();
487
+ };
488
+ history.replaceState = function(...args) {
489
+ originalReplace(...args);
490
+ onUrlChange();
491
+ };
492
+ return () => {
493
+ window.removeEventListener("popstate", onUrlChange);
494
+ history.pushState = originalPush;
495
+ history.replaceState = originalReplace;
496
+ };
497
+ }, [step, onComplete]);
498
+ }
499
+
500
+ // src/Spotlight.tsx
501
+ var import_jsx_runtime2 = require("react/jsx-runtime");
502
+ var PADDING = 4;
503
+ function Spotlight({ targetRect }) {
504
+ if (!targetRect) return null;
505
+ const top = targetRect.top - PADDING;
506
+ const left = targetRect.left - PADDING;
507
+ const width = targetRect.width + PADDING * 2;
508
+ const height = targetRect.height + PADDING * 2;
509
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
510
+ "div",
511
+ {
512
+ className: "__tg_spotlight",
513
+ style: {
514
+ position: "fixed",
515
+ top: 0,
516
+ left: 0,
517
+ width: "100vw",
518
+ height: "100vh",
519
+ zIndex: 9999,
520
+ pointerEvents: "none"
521
+ },
522
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
523
+ "svg",
524
+ {
525
+ width: "100%",
526
+ height: "100%",
527
+ style: { position: "absolute", top: 0, left: 0 },
528
+ children: [
529
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("mask", { id: "__tg_spotlight_mask", children: [
530
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }),
531
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
532
+ "rect",
533
+ {
534
+ x: left,
535
+ y: top,
536
+ width,
537
+ height,
538
+ rx: "4",
539
+ ry: "4",
540
+ fill: "black"
541
+ }
542
+ )
543
+ ] }) }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
545
+ "rect",
546
+ {
547
+ x: "0",
548
+ y: "0",
549
+ width: "100%",
550
+ height: "100%",
551
+ fill: "rgba(0,0,0,0.55)",
552
+ mask: "url(#__tg_spotlight_mask)"
553
+ }
554
+ )
555
+ ]
556
+ }
557
+ )
558
+ }
559
+ );
560
+ }
561
+
562
+ // src/TourTooltip.tsx
563
+ var import_jsx_runtime3 = require("react/jsx-runtime");
564
+ var PREFIX = "__tg_";
565
+ var styles = {
566
+ tooltip: {
567
+ position: "fixed",
568
+ zIndex: 1e4,
569
+ background: "#fff",
570
+ borderRadius: "8px",
571
+ boxShadow: "0 4px 24px rgba(0,0,0,0.18)",
572
+ padding: "16px",
573
+ maxWidth: "340px",
574
+ minWidth: "240px",
575
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
576
+ fontSize: "14px",
577
+ lineHeight: "1.5",
578
+ color: "#1a1a1a"
579
+ },
580
+ arrow: {
581
+ position: "absolute",
582
+ width: "16px",
583
+ height: "16px",
584
+ background: "#fff",
585
+ transform: "rotate(45deg)",
586
+ zIndex: -1,
587
+ boxShadow: "2px 2px 4px rgba(0,0,0,0.06)"
588
+ },
589
+ header: {
590
+ display: "flex",
591
+ justifyContent: "space-between",
592
+ alignItems: "flex-start",
593
+ marginBottom: "8px"
594
+ },
595
+ stepCounter: {
596
+ fontSize: "12px",
597
+ color: "#888",
598
+ fontWeight: 500,
599
+ flexShrink: 0
600
+ },
601
+ dismissBtn: {
602
+ background: "none",
603
+ border: "none",
604
+ cursor: "pointer",
605
+ fontSize: "18px",
606
+ color: "#999",
607
+ padding: "0 0 0 8px",
608
+ lineHeight: "1"
609
+ },
610
+ title: {
611
+ fontSize: "15px",
612
+ fontWeight: 600,
613
+ margin: "0 0 6px 0",
614
+ color: "#111"
615
+ },
616
+ body: {
617
+ margin: "0 0 12px 0",
618
+ color: "#444",
619
+ fontSize: "13px",
620
+ lineHeight: "1.55"
621
+ },
622
+ image: {
623
+ width: "100%",
624
+ borderRadius: "4px",
625
+ marginBottom: "12px"
626
+ },
627
+ footer: {
628
+ display: "flex",
629
+ justifyContent: "space-between",
630
+ alignItems: "center",
631
+ gap: "8px"
632
+ },
633
+ btn: {
634
+ padding: "6px 14px",
635
+ borderRadius: "6px",
636
+ fontSize: "13px",
637
+ fontWeight: 500,
638
+ cursor: "pointer",
639
+ border: "none"
640
+ },
641
+ btnPrimary: {
642
+ background: "#2563eb",
643
+ color: "#fff"
644
+ },
645
+ btnSecondary: {
646
+ background: "#f1f5f9",
647
+ color: "#334155"
648
+ }
649
+ };
650
+ function renderSimpleMarkdown(text) {
651
+ const parts = [];
652
+ const regex = /(\*\*(.+?)\*\*|\*(.+?)\*|`(.+?)`|\[(.+?)\]\((.+?)\))/g;
653
+ let lastIndex = 0;
654
+ let match;
655
+ let key = 0;
656
+ while ((match = regex.exec(text)) !== null) {
657
+ if (match.index > lastIndex) {
658
+ parts.push(text.slice(lastIndex, match.index));
659
+ }
660
+ if (match[2]) {
661
+ parts.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: match[2] }, key++));
662
+ } else if (match[3]) {
663
+ parts.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("em", { children: match[3] }, key++));
664
+ } else if (match[4]) {
665
+ parts.push(
666
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
667
+ "code",
668
+ {
669
+ style: {
670
+ background: "#f1f5f9",
671
+ padding: "1px 4px",
672
+ borderRadius: "3px",
673
+ fontSize: "12px"
674
+ },
675
+ children: match[4]
676
+ },
677
+ key++
678
+ )
679
+ );
680
+ } else if (match[5] && match[6]) {
681
+ parts.push(
682
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
683
+ "a",
684
+ {
685
+ href: match[6],
686
+ target: "_blank",
687
+ rel: "noopener noreferrer",
688
+ style: { color: "#2563eb", textDecoration: "underline" },
689
+ children: match[5]
690
+ },
691
+ key++
692
+ )
693
+ );
694
+ }
695
+ lastIndex = match.index + match[0].length;
696
+ }
697
+ if (lastIndex < text.length) {
698
+ parts.push(text.slice(lastIndex));
699
+ }
700
+ return parts;
701
+ }
702
+ function TourTooltip() {
703
+ const {
704
+ isActive,
705
+ currentStep,
706
+ currentStepIndex,
707
+ activeTour,
708
+ completeStep,
709
+ goToStep,
710
+ skipTour
711
+ } = useTour();
712
+ const targetEl = useElementDetection(currentStep?.target ?? null);
713
+ const tooltipRef = (0, import_react5.useRef)(null);
714
+ const [portalContainer, setPortalContainer] = (0, import_react5.useState)(null);
715
+ (0, import_react5.useEffect)(() => {
716
+ if (typeof document === "undefined") return;
717
+ let container = document.getElementById(`${PREFIX}portal`);
718
+ if (!container) {
719
+ container = document.createElement("div");
720
+ container.id = `${PREFIX}portal`;
721
+ document.body.appendChild(container);
722
+ }
723
+ setPortalContainer(container);
724
+ }, []);
725
+ const position = useTooltipPosition(
726
+ targetEl,
727
+ tooltipRef.current,
728
+ currentStep?.placement ?? "bottom"
729
+ );
730
+ useStepTrigger(currentStep, targetEl, completeStep);
731
+ if (!isActive || !currentStep || !activeTour || !portalContainer) {
732
+ return null;
733
+ }
734
+ const totalSteps = activeTour.steps.length;
735
+ const isFirst = currentStepIndex === 0;
736
+ const isLast = currentStepIndex === totalSteps - 1;
737
+ const content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
738
+ currentStep.spotlight && targetEl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spotlight, { targetRect: position.targetRect }),
739
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
740
+ "div",
741
+ {
742
+ ref: tooltipRef,
743
+ className: `${PREFIX}tooltip`,
744
+ style: {
745
+ ...styles.tooltip,
746
+ top: `${position.top}px`,
747
+ left: `${position.left}px`
748
+ },
749
+ children: [
750
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
751
+ "div",
752
+ {
753
+ style: {
754
+ ...styles.arrow,
755
+ top: `${position.arrowTop}px`,
756
+ left: `${position.arrowLeft}px`
757
+ }
758
+ }
759
+ ),
760
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles.header, children: [
761
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: styles.stepCounter, children: [
762
+ currentStepIndex + 1,
763
+ " of ",
764
+ totalSteps
765
+ ] }),
766
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
767
+ "button",
768
+ {
769
+ style: styles.dismissBtn,
770
+ onClick: skipTour,
771
+ "aria-label": "Dismiss tour",
772
+ children: "\xD7"
773
+ }
774
+ )
775
+ ] }),
776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h4", { style: styles.title, children: currentStep.title }),
777
+ currentStep.imageUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
778
+ "img",
779
+ {
780
+ src: currentStep.imageUrl,
781
+ alt: "",
782
+ style: styles.image
783
+ }
784
+ ),
785
+ currentStep.body && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.body, children: renderSimpleMarkdown(currentStep.body) }),
786
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles.footer, children: [
787
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: !isFirst && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
788
+ "button",
789
+ {
790
+ style: { ...styles.btn, ...styles.btnSecondary },
791
+ onClick: () => goToStep(currentStepIndex - 1),
792
+ children: "Back"
793
+ }
794
+ ) }),
795
+ currentStep.completionTrigger === "next-button" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
796
+ "button",
797
+ {
798
+ style: { ...styles.btn, ...styles.btnPrimary },
799
+ onClick: completeStep,
800
+ children: isLast ? "Finish" : "Next"
801
+ }
802
+ ),
803
+ currentStep.completionTrigger !== "next-button" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: "12px", color: "#888" }, children: currentStep.completionTrigger === "element-click" ? "Click the highlighted element" : currentStep.completionTrigger === "url-change" ? "Navigate to continue" : "Waiting..." })
804
+ ] })
805
+ ]
806
+ }
807
+ )
808
+ ] });
809
+ return (0, import_react_dom.createPortal)(content, portalContainer);
810
+ }
811
+ // Annotate the CommonJS export names for ESM import in node:
812
+ 0 && (module.exports = {
813
+ TourProvider,
814
+ TourTooltip,
815
+ useTour
816
+ });
817
+ //# sourceMappingURL=index.js.map