ontheway-sdk 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.
Files changed (44) hide show
  1. package/README.md +110 -0
  2. package/dist/checklist.cjs +319 -0
  3. package/dist/checklist.cjs.map +1 -0
  4. package/dist/checklist.d.cts +58 -0
  5. package/dist/checklist.d.ts +58 -0
  6. package/dist/checklist.js +314 -0
  7. package/dist/checklist.js.map +1 -0
  8. package/dist/chunk-254YHUN3.cjs +26 -0
  9. package/dist/chunk-254YHUN3.cjs.map +1 -0
  10. package/dist/chunk-DDAAVRWG.js +23 -0
  11. package/dist/chunk-DDAAVRWG.js.map +1 -0
  12. package/dist/chunk-NRUQU5AR.cjs +94 -0
  13. package/dist/chunk-NRUQU5AR.cjs.map +1 -0
  14. package/dist/chunk-OKJ5GEH3.js +358 -0
  15. package/dist/chunk-OKJ5GEH3.js.map +1 -0
  16. package/dist/chunk-RNQLNLNI.js +91 -0
  17. package/dist/chunk-RNQLNLNI.js.map +1 -0
  18. package/dist/chunk-UE3T6TSM.cjs +361 -0
  19. package/dist/chunk-UE3T6TSM.cjs.map +1 -0
  20. package/dist/components.cjs +211 -0
  21. package/dist/components.cjs.map +1 -0
  22. package/dist/components.d.cts +51 -0
  23. package/dist/components.d.ts +51 -0
  24. package/dist/components.js +205 -0
  25. package/dist/components.js.map +1 -0
  26. package/dist/devtools.cjs +733 -0
  27. package/dist/devtools.cjs.map +1 -0
  28. package/dist/devtools.d.cts +18 -0
  29. package/dist/devtools.d.ts +18 -0
  30. package/dist/devtools.js +727 -0
  31. package/dist/devtools.js.map +1 -0
  32. package/dist/index.cjs +19 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.cts +163 -0
  35. package/dist/index.d.ts +163 -0
  36. package/dist/index.js +4 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/react.cjs +18 -0
  39. package/dist/react.cjs.map +1 -0
  40. package/dist/react.d.cts +68 -0
  41. package/dist/react.d.ts +68 -0
  42. package/dist/react.js +5 -0
  43. package/dist/react.js.map +1 -0
  44. package/package.json +93 -0
@@ -0,0 +1,358 @@
1
+ import { __spreadValues } from './chunk-DDAAVRWG.js';
2
+ import { driver } from 'driver.js';
3
+
4
+ var DRIVER_CSS_CDN = "https://cdn.jsdelivr.net/npm/driver.js@1.3.1/dist/driver.css";
5
+ function loadDriverCSS() {
6
+ if (typeof document === "undefined") return;
7
+ if (document.querySelector("link[data-otw-driver-css]")) return;
8
+ const link = document.createElement("link");
9
+ link.rel = "stylesheet";
10
+ link.href = DRIVER_CSS_CDN;
11
+ link.setAttribute("data-otw-driver-css", "1");
12
+ document.head.appendChild(link);
13
+ }
14
+ var CROSS_PAGE_KEY = "otw_active_tour";
15
+ var OnTheWay = class {
16
+ constructor(config) {
17
+ this.driverInstance = null;
18
+ /** Condition functions registered via `registerCondition` */
19
+ this.conditions = /* @__PURE__ */ new Map();
20
+ this.config = __spreadValues({
21
+ apiUrl: "/api"
22
+ }, config);
23
+ this.state = {
24
+ loaded: false,
25
+ tasks: [],
26
+ completedTasks: /* @__PURE__ */ new Set()
27
+ };
28
+ if (config.driverCssUrl !== false) {
29
+ if (config.driverCssUrl) {
30
+ if (typeof document !== "undefined" && !document.querySelector("link[data-otw-driver-css]")) {
31
+ const link = document.createElement("link");
32
+ link.rel = "stylesheet";
33
+ link.href = config.driverCssUrl;
34
+ link.setAttribute("data-otw-driver-css", "1");
35
+ document.head.appendChild(link);
36
+ }
37
+ } else {
38
+ loadDriverCSS();
39
+ }
40
+ }
41
+ this.visitorId = this.getOrCreateVisitorId();
42
+ this.init();
43
+ }
44
+ async init() {
45
+ this.loadCompletedTasks();
46
+ await this.fetchTasks();
47
+ this.state.loaded = true;
48
+ if (!this.resumeCrossPageTour()) {
49
+ this.handleAutoStart();
50
+ }
51
+ }
52
+ getOrCreateVisitorId() {
53
+ if (typeof localStorage === "undefined") return "v_ssr";
54
+ const key = "otw_visitor_id";
55
+ let id = localStorage.getItem(key);
56
+ if (!id) {
57
+ id = "v_" + Math.random().toString(36).substring(2) + Date.now().toString(36);
58
+ localStorage.setItem(key, id);
59
+ }
60
+ return id;
61
+ }
62
+ loadCompletedTasks() {
63
+ if (typeof localStorage === "undefined") return;
64
+ const key = `otw_completed_${this.config.projectId}`;
65
+ const completed = localStorage.getItem(key);
66
+ if (completed) {
67
+ this.state.completedTasks = new Set(JSON.parse(completed));
68
+ }
69
+ }
70
+ saveCompletedTask(taskId) {
71
+ this.state.completedTasks.add(taskId);
72
+ if (typeof localStorage === "undefined") return;
73
+ const key = `otw_completed_${this.config.projectId}`;
74
+ localStorage.setItem(key, JSON.stringify([...this.state.completedTasks]));
75
+ }
76
+ async fetchTasks() {
77
+ try {
78
+ const res = await fetch(`${this.config.apiUrl}/sdk/${this.config.projectId}/config`);
79
+ if (!res.ok) throw new Error("Failed to fetch config");
80
+ const data = await res.json();
81
+ this.state.tasks = data.tasks || [];
82
+ } catch (error) {
83
+ console.warn("[OnTheWay] Failed to load config:", error);
84
+ }
85
+ }
86
+ handleAutoStart() {
87
+ var _a, _b, _c;
88
+ if (typeof window === "undefined") return;
89
+ const currentUrl = window.location.href;
90
+ for (const task of this.state.tasks) {
91
+ if (this.state.completedTasks.has(task.id)) continue;
92
+ if ((_a = task.targeting) == null ? void 0 : _a.urlPattern) {
93
+ const pattern = new RegExp(task.targeting.urlPattern);
94
+ if (!pattern.test(currentUrl)) continue;
95
+ }
96
+ if (task.trigger === "auto") {
97
+ this.start(task.slug);
98
+ break;
99
+ } else if (task.trigger === "first-visit") {
100
+ if (typeof localStorage === "undefined") continue;
101
+ const visitKey = `otw_visited_${task.id}`;
102
+ if (!localStorage.getItem(visitKey)) {
103
+ localStorage.setItem(visitKey, "true");
104
+ this.start(task.slug);
105
+ break;
106
+ }
107
+ } else if (task.trigger === "condition") {
108
+ const conditionFn = (_c = this.conditions.get(task.slug)) != null ? _c : (_b = task.targeting) == null ? void 0 : _b.condition;
109
+ if (conditionFn && conditionFn()) {
110
+ this.start(task.slug);
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ // ---- Cross-page tour helpers ----
117
+ /**
118
+ * Save cross-page tour state so it survives navigation.
119
+ * @internal
120
+ */
121
+ saveCrossPageState(taskSlug, stepIndex) {
122
+ if (typeof sessionStorage === "undefined") return;
123
+ const state = {
124
+ taskSlug,
125
+ stepIndex,
126
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
127
+ };
128
+ sessionStorage.setItem(CROSS_PAGE_KEY, JSON.stringify(state));
129
+ }
130
+ /**
131
+ * Clear cross-page tour state.
132
+ * @internal
133
+ */
134
+ clearCrossPageState() {
135
+ if (typeof sessionStorage === "undefined") return;
136
+ sessionStorage.removeItem(CROSS_PAGE_KEY);
137
+ }
138
+ /**
139
+ * Attempt to resume a cross-page tour from sessionStorage.
140
+ * @returns `true` if a tour was resumed, `false` otherwise.
141
+ * @internal
142
+ */
143
+ resumeCrossPageTour() {
144
+ if (typeof sessionStorage === "undefined") return false;
145
+ const raw = sessionStorage.getItem(CROSS_PAGE_KEY);
146
+ if (!raw) return false;
147
+ try {
148
+ const state = JSON.parse(raw);
149
+ const task = this.state.tasks.find((t) => t.slug === state.taskSlug);
150
+ if (!task) {
151
+ this.clearCrossPageState();
152
+ return false;
153
+ }
154
+ this.startAtStep(task, state.stepIndex);
155
+ return true;
156
+ } catch (e) {
157
+ this.clearCrossPageState();
158
+ return false;
159
+ }
160
+ }
161
+ /**
162
+ * Check whether a step's URL matches the current page.
163
+ * @internal
164
+ */
165
+ stepUrlMatches(stepUrl) {
166
+ if (!stepUrl) return true;
167
+ if (typeof window === "undefined") return true;
168
+ const current = window.location.pathname + window.location.search;
169
+ try {
170
+ const parsed = new URL(stepUrl, window.location.origin);
171
+ return parsed.pathname === window.location.pathname && parsed.search === window.location.search;
172
+ } catch (e) {
173
+ return current === stepUrl;
174
+ }
175
+ }
176
+ /**
177
+ * Start a task at a specific step index (used for cross-page resume).
178
+ * @internal
179
+ */
180
+ startAtStep(task, fromIndex) {
181
+ const allSteps = task.steps;
182
+ const targetStep = allSteps[fromIndex];
183
+ if ((targetStep == null ? void 0 : targetStep.url) && !this.stepUrlMatches(targetStep.url)) {
184
+ this.saveCrossPageState(task.slug, fromIndex);
185
+ window.location.href = targetStep.url;
186
+ return;
187
+ }
188
+ const steps = allSteps.slice(fromIndex).map((step) => ({
189
+ element: step.element,
190
+ popover: {
191
+ title: step.popover.title,
192
+ description: step.popover.description,
193
+ side: step.popover.side
194
+ }
195
+ }));
196
+ const totalSteps = allSteps.length;
197
+ this.driverInstance = driver({
198
+ showProgress: true,
199
+ steps,
200
+ onNextClick: () => {
201
+ var _a;
202
+ if (!this.driverInstance) return;
203
+ const relativeIndex = (_a = this.driverInstance.getActiveIndex()) != null ? _a : 0;
204
+ const absoluteIndex = fromIndex + relativeIndex + 1;
205
+ if (absoluteIndex < allSteps.length) {
206
+ const nextStep = allSteps[absoluteIndex];
207
+ if (nextStep.url && !this.stepUrlMatches(nextStep.url)) {
208
+ this.saveCrossPageState(task.slug, absoluteIndex);
209
+ this.driverInstance.destroy();
210
+ window.location.href = nextStep.url;
211
+ return;
212
+ }
213
+ }
214
+ this.driverInstance.moveNext();
215
+ },
216
+ onDestroyStarted: () => {
217
+ var _a, _b, _c, _d, _e, _f;
218
+ if ((_a = this.driverInstance) == null ? void 0 : _a.hasNextStep()) {
219
+ const relativeIndex = this.driverInstance.getActiveIndex() || 0;
220
+ const currentIndex = fromIndex + relativeIndex;
221
+ (_c = (_b = this.config).onSkip) == null ? void 0 : _c.call(_b, task.id, currentIndex);
222
+ this.trackCompletion(task.id, currentIndex, totalSteps, false);
223
+ this.clearCrossPageState();
224
+ } else {
225
+ this.saveCompletedTask(task.id);
226
+ (_e = (_d = this.config).onComplete) == null ? void 0 : _e.call(_d, task.id);
227
+ this.trackCompletion(task.id, totalSteps, totalSteps, true);
228
+ this.clearCrossPageState();
229
+ }
230
+ (_f = this.driverInstance) == null ? void 0 : _f.destroy();
231
+ }
232
+ });
233
+ this.driverInstance.drive();
234
+ }
235
+ /**
236
+ * Register a condition function for a task slug.
237
+ * When the task trigger is `'condition'`, this function will be evaluated
238
+ * during auto-start to decide whether to show the tour.
239
+ *
240
+ * @param slug - Task slug to attach the condition to
241
+ * @param fn - Predicate that returns `true` to trigger the tour
242
+ */
243
+ registerCondition(slug, fn) {
244
+ this.conditions.set(slug, fn);
245
+ }
246
+ /**
247
+ * Re-evaluate conditions and auto-start eligible tasks.
248
+ * Call this when application state changes (e.g. data loaded) and
249
+ * condition-based tours should be re-checked.
250
+ */
251
+ checkConditions() {
252
+ this.handleAutoStart();
253
+ }
254
+ /**
255
+ * Start a task by slug or ID
256
+ */
257
+ start(slugOrId) {
258
+ const task = this.state.tasks.find((t) => t.slug === slugOrId || t.id === slugOrId);
259
+ if (!task) {
260
+ console.warn(`[OnTheWay] Task not found: ${slugOrId}`);
261
+ return;
262
+ }
263
+ this.startAtStep(task, 0);
264
+ }
265
+ /**
266
+ * Reset a task (allow it to show again)
267
+ */
268
+ reset(slugOrId) {
269
+ const task = this.state.tasks.find((t) => t.slug === slugOrId || t.id === slugOrId);
270
+ if (task) {
271
+ this.state.completedTasks.delete(task.id);
272
+ if (typeof localStorage !== "undefined") {
273
+ const key = `otw_completed_${this.config.projectId}`;
274
+ localStorage.setItem(key, JSON.stringify([...this.state.completedTasks]));
275
+ localStorage.removeItem(`otw_visited_${task.id}`);
276
+ }
277
+ }
278
+ }
279
+ /**
280
+ * Reset all tasks for this project
281
+ */
282
+ resetAll() {
283
+ this.state.completedTasks.clear();
284
+ if (typeof localStorage !== "undefined") {
285
+ localStorage.removeItem(`otw_completed_${this.config.projectId}`);
286
+ this.state.tasks.forEach((task) => {
287
+ localStorage.removeItem(`otw_visited_${task.id}`);
288
+ });
289
+ }
290
+ }
291
+ async trackCompletion(taskId, stepsCompleted, totalSteps, completed) {
292
+ try {
293
+ await fetch(`${this.config.apiUrl}/sdk/track`, {
294
+ method: "POST",
295
+ headers: { "Content-Type": "application/json" },
296
+ body: JSON.stringify({
297
+ task_id: taskId,
298
+ visitor_id: this.visitorId,
299
+ steps_completed: stepsCompleted,
300
+ total_steps: totalSteps,
301
+ completed
302
+ })
303
+ });
304
+ } catch (e) {
305
+ }
306
+ }
307
+ /**
308
+ * Get the project ID this SDK instance was configured with.
309
+ */
310
+ getProjectId() {
311
+ return this.config.projectId;
312
+ }
313
+ /**
314
+ * Check if SDK is loaded
315
+ */
316
+ isReady() {
317
+ return this.state.loaded;
318
+ }
319
+ /**
320
+ * Check if a task has been completed
321
+ */
322
+ isTaskCompleted(slugOrId) {
323
+ const task = this.state.tasks.find((t) => t.slug === slugOrId || t.id === slugOrId);
324
+ if (!task) return false;
325
+ return this.state.completedTasks.has(task.id);
326
+ }
327
+ /**
328
+ * Get list of available tasks
329
+ */
330
+ getTasks() {
331
+ return [...this.state.tasks];
332
+ }
333
+ /**
334
+ * Get the set of completed task IDs
335
+ */
336
+ getCompletedTaskIds() {
337
+ return new Set(this.state.completedTasks);
338
+ }
339
+ /**
340
+ * Get pending cross-page tour state, if any.
341
+ * Useful for the React provider to check on mount.
342
+ */
343
+ static getCrossPageState() {
344
+ if (typeof sessionStorage === "undefined") return null;
345
+ const raw = sessionStorage.getItem(CROSS_PAGE_KEY);
346
+ if (!raw) return null;
347
+ try {
348
+ return JSON.parse(raw);
349
+ } catch (e) {
350
+ return null;
351
+ }
352
+ }
353
+ };
354
+ var src_default = OnTheWay;
355
+
356
+ export { OnTheWay, src_default };
357
+ //# sourceMappingURL=chunk-OKJ5GEH3.js.map
358
+ //# sourceMappingURL=chunk-OKJ5GEH3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"chunk-OKJ5GEH3.js","sourcesContent":["/**\n * OnTheWay SDK\n * Lightweight onboarding SDK based on Driver.js\n *\n * Usage:\n * import { OnTheWay } from '@ontheway/sdk'\n * const otw = new OnTheWay({ projectId: 'PROJECT_ID' })\n * otw.start('task-slug')\n */\n\nimport { driver, type Driver, type DriveStep } from 'driver.js'\n\n// ---- Runtime CSS injection ----\n\nconst DRIVER_CSS_CDN = 'https://cdn.jsdelivr.net/npm/driver.js@1.3.1/dist/driver.css'\n\nfunction loadDriverCSS() {\n if (typeof document === 'undefined') return\n if (document.querySelector('link[data-otw-driver-css]')) return\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = DRIVER_CSS_CDN\n link.setAttribute('data-otw-driver-css', '1')\n document.head.appendChild(link)\n}\n\n// ---- Cross-page Tour Storage ----\n\n/** @internal Key used in sessionStorage for persisting active cross-page tour state */\nconst CROSS_PAGE_KEY = 'otw_active_tour'\n\n/**\n * State persisted to sessionStorage so a tour can survive page navigations.\n */\nexport interface CrossPageTourState {\n /** Task slug that is currently running */\n taskSlug: string\n /** Index of the step to resume from */\n stepIndex: number\n /** ISO timestamp when the tour started */\n startedAt: string\n}\n\n// ---- Types ----\n\n/** Configuration passed to the OnTheWay constructor */\nexport interface OnTheWayConfig {\n projectId: string\n apiUrl?: string\n /** Custom Driver.js CSS URL. Set to `false` to disable auto-injection. */\n driverCssUrl?: string | false\n onComplete?: (taskId: string) => void\n onSkip?: (taskId: string, stepIndex: number) => void\n}\n\n/** A single task returned by the server or configured locally */\nexport interface TaskConfig {\n id: string\n slug: string\n trigger: 'auto' | 'manual' | 'first-visit' | 'condition'\n steps: StepConfig[]\n targeting?: {\n urlPattern?: string\n newUsersOnly?: boolean\n /**\n * Condition callback evaluated at auto-start time.\n * Return `true` to trigger the tour.\n * Only used when `trigger === 'condition'`.\n */\n condition?: () => boolean\n }\n}\n\n/** Configuration for a single step in a tour */\nexport interface StepConfig {\n element: string\n popover: {\n title: string\n description: string\n side?: 'top' | 'bottom' | 'left' | 'right'\n }\n /**\n * Optional URL this step should be shown on.\n * If the current page does not match, the SDK will navigate to it and\n * resume the tour aftehe page loads.\n */\n url?: string\n}\n\n/** @internal Runtime state of the SDK */\nexport interface SDKState {\n loaded: boolean\n tasks: TaskConfig[]\n completedTasks: Set<string>\n}\n\nexport class OnTheWay {\n private config: OnTheWayConfig\n private state: SDKState\n private driverInstance: Driver | null = null\n private visitorId: string\n /** Condition functions registered via `registerCondition` */\n private conditions: Map<string, () => boolean> = new Map()\n\n constructor(config: OnTheWayConfig) {\n this.config = {\n apiUrl: '/api',\n ...config,\n }\n this.state = {\n loaded: false,\n tasks: [],\n completedTasks: new Set(),\n }\n\n // Inject driver.js CSS at runtime (unless explicitly disabled)\n if (config.driverCssUrl !== false) {\n if (config.driverCssUrl) {\n // User provided a custom CSS URL\n if (typeof document !== 'undefined' && !document.querySelector('link[data-otw-driver-css]')) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = config.driverCssUrl\n link.setAttribute('data-otw-driver-css', '1')\n document.head.appendChild(link)\n }\n } else {\n loadDriverCSS()\n }\n }\n\n this.visitorId = this.getOrCreateVisitorId()\n this.init()\n }\n\n private async init() {\n // Load completed tasks from localStorage\n this.loadCompletedTasks()\n\n // Fetch task configs\n await this.fetchTasks()\n\n this.state.loaded = true\n\n // Check for cross-page tour resume before auto-start\n if (!this.resumeCrossPageTour()) {\n // Auto-start tasks if configured\n this.handleAutoStart()\n }\n }\n\n private getOrCreateVisitorId(): string {\n if (typeof localStorage === 'undefined') return 'v_ssr'\n const key = 'otw_visitor_id'\n let id = localStorage.getItem(key)\n if (!id) {\n id = 'v_' + Math.random().toString(36).substring(2) + Date.now().toString(36)\n localStorage.setItem(key, id)\n }\n return id\n }\n\n private loadCompletedTasks() {\n if (typeof localStorage === 'undefined') return\n const key = `otw_completed_${this.config.projectId}`\n const completed = localStorage.getItem(key)\n if (completed) {\n this.state.completedTasks = new Set(JSON.parse(completed))\n }\n }\n\n private saveCompletedTask(taskId: string) {\n this.state.completedTasks.add(taskId)\n if (typeof localStorage === 'undefined') return\n const key = `otw_completed_${this.config.projectId}`\n localStorage.setItem(key, JSON.stringify([...this.state.completedTasks]))\n }\n\n private async fetchTasks() {\n try {\n const res = await fetch(`${this.config.apiUrl}/sdk/${this.config.projectId}/config`)\n if (!res.ok) throw new Error('Failed to fetch config')\n const data = await res.json()\n this.state.tasks = data.tasks || []\n } catch (error) {\n console.warn('[OnTheWay] Failed to load config:', error)\n }\n }\n\n private handleAutoStart() {\n if (typeof window === 'undefined') return\n const currentUrl = window.location.href\n\n for (const task of this.state.tasks) {\n // Skip completed tasks\n if (this.state.completedTasks.has(task.id)) continue\n\n // Check URL targeting\n if (task.targeting?.urlPattern) {\n const pattern = new RegExp(task.targeting.urlPattern)\n if (!pattern.test(currentUrl)) continue\n }\n\n // Handle trigger types\n if (task.trigger === 'auto') {\n this.start(task.slug)\n break // Only one auto task at a time\n } else if (task.trigger === 'first-visit') {\n if (typeof localStorage === 'undefined') continue\n const visitKey = `otw_visited_${task.id}`\n if (!localStorage.getItem(visitKey)) {\n localStorage.setItem(visitKey, 'true')\n this.start(task.slug)\n break\n }\n } else if (task.trigger === 'condition') {\n // Check registered condition or inline condition\n const conditionFn =\n this.conditions.get(task.slug) ?? task.targeting?.condition\n if (conditionFn && conditionFn()) {\n this.start(task.slug)\n break\n }\n }\n }\n }\n\n // ---- Cross-page tour helpers ----\n\n /**\n * Save cross-page tour state so it survives navigation.\n * @internal\n */\n private saveCrossPageState(taskSlug: string, stepIndex: number) {\n if (typeof sessionStorage === 'undefined') return\n const state: CrossPageTourState = {\n taskSlug,\n stepIndex,\n startedAt: new Date().toISOString(),\n }\n sessionStorage.setItem(CROSS_PAGE_KEY, JSON.stringify(state))\n }\n\n /**\n * Clear cross-page tour state.\n * @internal\n */\n private clearCrossPageState() {\n if (typeof sessionStorage === 'undefined') return\n sessionStorage.removeItem(CROSS_PAGE_KEY)\n }\n\n /**\n * Attempt to resume a cross-page tour from sessionStorage.\n * @returns `true` if a tour was resumed, `false` otherwise.\n * @internal\n */\n private resumeCrossPageTour(): boolean {\n if (typeof sessionStorage === 'undefined') return false\n const raw = sessionStorage.getItem(CROSS_PAGE_KEY)\n if (!raw) return false\n\n try {\n const state: CrossPageTourState = JSON.parse(raw)\n const task = this.state.tasks.find(t => t.slug === state.taskSlug)\n if (!task) {\n this.clearCrossPageState()\n return false\n }\n // Resume from the saved step index\n this.startAtStep(task, state.stepIndex)\n return true\n } catch {\n this.clearCrossPageState()\n return false\n }\n }\n\n /**\n * Check whether a step's URL matches the current page.\n * @internal\n */\n private stepUrlMatches(stepUrl: string | undefined): boolean {\n if (!stepUrl) return true // no url constraint = always matches\n if (typeof window === 'undefined') return true\n const current = window.location.pathname + window.location.search\n // Support both full URLs and path-only\n try {\n const parsed = new URL(stepUrl, window.location.origin)\n return (\n parsed.pathname === window.location.pathname &&\n parsed.search === window.location.search\n )\n } catch {\n return current === stepUrl\n }\n }\n\n /**\n * Start a task at a specific step index (used for cross-page resume).\n * @internal\n */\n private startAtStep(task: TaskConfig, fromIndex: number) {\n const allSteps = task.steps\n\n // Check if the target step's URL matches the current page\n const targetStep = allSteps[fromIndex]\n if (targetStep?.url && !this.stepUrlMatches(targetStep.url)) {\n // Need to navigate — save state and redirect\n this.saveCrossPageState(task.slug, fromIndex)\n window.location.href = targetStep.url\n return\n }\n\n // Build Driver.js steps from fromIndex onward\n const steps: DriveStep[] = allSteps.slice(fromIndex).map(step => ({\n element: step.element,\n popover: {\n title: step.popover.title,\n description: step.popover.description,\n side: step.popover.side,\n },\n }))\n\n const totalSteps = allSteps.length\n\n this.driverInstance = driver({\n showProgress: true,\n steps,\n onNextClick: () => {\n if (!this.driverInstance) return\n const relativeIndex = this.driverInstance.getActiveIndex() ?? 0\n const absoluteIndex = fromIndex + relativeIndex + 1\n\n // Check if next step requires a different page\n if (absoluteIndex < allSteps.length) {\n const nextStep = allSteps[absoluteIndex]\n if (nextStep.url && !this.stepUrlMatches(nextStep.url)) {\n this.saveCrossPageState(task.slug, absoluteIndex)\n this.driverInstance.destroy()\n window.location.href = nextStep.url\n return\n }\n }\n\n this.driverInstance.moveNext()\n },\n onDestroyStarted: () => {\n if (this.driverInstance?.hasNextStep()) {\n // Skipped\n const relativeIndex = this.driverInstance.getActiveIndex() || 0\n const currentIndex = fromIndex + relativeIndex\n this.config.onSkip?.(task.id, currentIndex)\n this.trackCompletion(task.id, currentIndex, totalSteps, false)\n this.clearCrossPageState()\n } else {\n // Completed\n this.saveCompletedTask(task.id)\n this.config.onComplete?.(task.id)\n this.trackCompletion(task.id, totalSteps, totalSteps, true)\n this.clearCrossPageState()\n }\n this.driverInstance?.destroy()\n },\n })\n\n this.driverInstance.drive()\n }\n\n /**\n * Register a condition function for a task slug.\n * When the task trigger is `'condition'`, this function will be evaluated\n * during auto-start to decide whether to show the tour.\n *\n * @param slug - Task slug to attach the condition to\n * @param fn - Predicate that returns `true` to trigger the tour\n */\n public registerCondition(slug: string, fn: () => boolean) {\n this.conditions.set(slug, fn)\n }\n\n /**\n * Re-evaluate conditions and auto-start eligible tasks.\n * Call this when application state changes (e.g. data loaded) and\n * condition-based tours should be re-checked.\n */\n public checkConditions() {\n this.handleAutoStart()\n }\n\n /**\n * Start a task by slug or ID\n */\n public start(slugOrId: string) {\n const task = this.state.tasks.find(t => t.slug === slugOrId || t.id === slugOrId)\n if (!task) {\n console.warn(`[OnTheWay] Task not found: ${slugOrId}`)\n return\n }\n\n this.startAtStep(task, 0)\n }\n\n /**\n * Reset a task (allow it to show again)\n */\n public reset(slugOrId: string) {\n const task = this.state.tasks.find(t => t.slug === slugOrId || t.id === slugOrId)\n if (task) {\n this.state.completedTasks.delete(task.id)\n if (typeof localStorage !== 'undefined') {\n const key = `otw_completed_${this.config.projectId}`\n localStorage.setItem(key, JSON.stringify([...this.state.completedTasks]))\n // Also reset first-visit flag\n localStorage.removeItem(`otw_visited_${task.id}`)\n }\n }\n }\n\n /**\n * Reset all tasks for this project\n */\n public resetAll() {\n this.state.completedTasks.clear()\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem(`otw_completed_${this.config.projectId}`)\n this.state.tasks.forEach(task => {\n localStorage.removeItem(`otw_visited_${task.id}`)\n })\n }\n }\n\n private async trackCompletion(\n taskId: string,\n stepsCompleted: number,\n totalSteps: number,\n completed: boolean,\n ) {\n try {\n await fetch(`${this.config.apiUrl}/sdk/track`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n task_id: taskId,\n visitor_id: this.visitorId,\n steps_completed: stepsCompleted,\n total_steps: totalSteps,\n completed,\n }),\n })\n } catch {\n // Silent fail for analytics\n }\n }\n\n /**\n * Get the project ID this SDK instance was configured with.\n */\n public getProjectId(): string {\n return this.config.projectId\n }\n\n /**\n * Check if SDK is loaded\n */\n public isReady(): boolean {\n return this.state.loaded\n }\n\n /**\n * Check if a task has been completed\n */\n public isTaskCompleted(slugOrId: string): boolean {\n const task = this.state.tasks.find(t => t.slug === slugOrId || t.id === slugOrId)\n if (!task) return false\n return this.state.completedTasks.has(task.id)\n }\n\n /**\n * Get list of available tasks\n */\n public getTasks(): TaskConfig[] {\n return [...this.state.tasks]\n }\n\n /**\n * Get the set of completed task IDs\n */\n public getCompletedTaskIds(): Set<string> {\n return new Set(this.state.completedTasks)\n }\n\n /**\n * Get pending cross-page tour state, if any.\n * Useful for the React provider to check on mount.\n */\n public static getCrossPageState(): CrossPageTourState | null {\n if (typeof sessionStorage === 'undefined') return null\n const raw = sessionStorage.getItem(CROSS_PAGE_KEY)\n if (!raw) return null\n try {\n return JSON.parse(raw)\n } catch {\n return null\n }\n }\n}\n\nexport default OnTheWay\n"]}
@@ -0,0 +1,91 @@
1
+ import { OnTheWay } from './chunk-OKJ5GEH3.js';
2
+ import { createContext, useState, useEffect, useCallback, useContext } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var OnTheWayContext = createContext({
6
+ otw: null,
7
+ ready: false,
8
+ start: () => {
9
+ },
10
+ reset: () => {
11
+ },
12
+ resetAll: () => {
13
+ },
14
+ registerCondition: () => {
15
+ },
16
+ checkConditions: () => {
17
+ },
18
+ isTaskCompleted: () => false
19
+ });
20
+ function OnTheWayProvider({
21
+ projectId,
22
+ apiUrl,
23
+ onComplete,
24
+ onSkip,
25
+ children
26
+ }) {
27
+ const [otw, setOtw] = useState(null);
28
+ const [ready, setReady] = useState(false);
29
+ useEffect(() => {
30
+ const instance = new OnTheWay({
31
+ projectId,
32
+ apiUrl,
33
+ onComplete,
34
+ onSkip
35
+ });
36
+ setOtw(instance);
37
+ const check = setInterval(() => {
38
+ if (instance.isReady()) {
39
+ setReady(true);
40
+ clearInterval(check);
41
+ }
42
+ }, 100);
43
+ return () => {
44
+ clearInterval(check);
45
+ };
46
+ }, [projectId, apiUrl]);
47
+ const start = useCallback(
48
+ (slugOrId) => otw == null ? void 0 : otw.start(slugOrId),
49
+ [otw]
50
+ );
51
+ const reset = useCallback(
52
+ (slugOrId) => otw == null ? void 0 : otw.reset(slugOrId),
53
+ [otw]
54
+ );
55
+ const resetAll = useCallback(() => otw == null ? void 0 : otw.resetAll(), [otw]);
56
+ const registerCondition = useCallback(
57
+ (slug, fn) => otw == null ? void 0 : otw.registerCondition(slug, fn),
58
+ [otw]
59
+ );
60
+ const checkConditions = useCallback(() => otw == null ? void 0 : otw.checkConditions(), [otw]);
61
+ const isTaskCompleted = useCallback(
62
+ (slugOrId) => {
63
+ var _a;
64
+ return (_a = otw == null ? void 0 : otw.isTaskCompleted(slugOrId)) != null ? _a : false;
65
+ },
66
+ [otw]
67
+ );
68
+ return /* @__PURE__ */ jsx(
69
+ OnTheWayContext.Provider,
70
+ {
71
+ value: {
72
+ otw,
73
+ ready,
74
+ start,
75
+ reset,
76
+ resetAll,
77
+ registerCondition,
78
+ checkConditions,
79
+ isTaskCompleted
80
+ },
81
+ children
82
+ }
83
+ );
84
+ }
85
+ function useOnTheWay() {
86
+ return useContext(OnTheWayContext);
87
+ }
88
+
89
+ export { OnTheWayProvider, useOnTheWay };
90
+ //# sourceMappingURL=chunk-RNQLNLNI.js.map
91
+ //# sourceMappingURL=chunk-RNQLNLNI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":"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","file":"chunk-RNQLNLNI.js","sourcesContent":["'use client'\n\nimport { createContext, useContext, useEffect, useState, useCallback, useRef, type ReactNode } from 'react'\nimport { OnTheWay } from './index'\n\n// ---- Types ----\n\n/** Context value exposed by the OnTheWay provider */\nexport interface OnTheWayContextValue {\n /** SDK instance, null before initialisation */\n otw: OnTheWay | null\n /** Whether the SDK has finished loading */\n ready: boolean\n /** Start a tour by slug or ID */\n start: (slugOrId: string) => void\n /** Reset a specific task */\n reset: (slugOrId: string) => void\n /** Reset all tasks */\n resetAll: () => void\n /**\n * Register a condition function for conditional triggers.\n * The function is evaluated during auto-start when the task trigger is `'condition'`.\n */\n registerCondition: (slug: string, fn: () => boolean) => void\n /**\n * Re-evaluate conditions and start eligible tours.\n * Call after state changes that might satisfy a condition.\n */\n checkConditions: () => void\n /** Check whether a given task has been completed */\n isTaskCompleted: (slugOrId: string) => boolean\n}\n\n/** Props for the OnTheWayProvider component */\nexport interface OnTheWayProviderProps {\n projectId: string\n apiUrl?: string\n onComplete?: (taskId: string) => void\n onSkip?: (taskId: string, stepIndex: number) => void\n children: ReactNode\n}\n\n// ---- Context ----\n\nconst OnTheWayContext = createContext<OnTheWayContextValue>({\n otw: null,\n ready: false,\n start: () => {},\n reset: () => {},\n resetAll: () => {},\n registerCondition: () => {},\n checkConditions: () => {},\n isTaskCompleted: () => false,\n})\n\n// ---- Provider ----\n\n/**\n * Provides the OnTheWay SDK to descendant components.\n *\n * Wrap your application (or a subtree) with this provider so that\n * `useOnTheWay()` returns the SDK instance.\n *\n * On mount the provider also checks for any pending cross-page tour\n * (persisted in `sessionStorage`) and resumes it automatically.\n *\n * @example\n * ```tsx\n * <OnTheWayProvider projectId=\"proj_abc\">\n * <App />\n * </OnTheWayProvider>\n * ```\n */\nexport function OnTheWayProvider({\n projectId,\n apiUrl,\n onComplete,\n onSkip,\n children,\n}: OnTheWayProviderProps) {\n const [otw, setOtw] = useState<OnTheWay | null>(null)\n const [ready, setReady] = useState(false)\n\n useEffect(() => {\n const instance = new OnTheWay({\n projectId,\n apiUrl,\n onComplete,\n onSkip,\n })\n\n setOtw(instance)\n\n // Poll until ready (cross-page resume happens inside init)\n const check = setInterval(() => {\n if (instance.isReady()) {\n setReady(true)\n clearInterval(check)\n }\n }, 100)\n\n return () => {\n clearInterval(check)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [projectId, apiUrl])\n\n const start = useCallback(\n (slugOrId: string) => otw?.start(slugOrId),\n [otw],\n )\n\n const reset = useCallback(\n (slugOrId: string) => otw?.reset(slugOrId),\n [otw],\n )\n\n const resetAll = useCallback(() => otw?.resetAll(), [otw])\n\n const registerCondition = useCallback(\n (slug: string, fn: () => boolean) => otw?.registerCondition(slug, fn),\n [otw],\n )\n\n const checkConditions = useCallback(() => otw?.checkConditions(), [otw])\n\n const isTaskCompleted = useCallback(\n (slugOrId: string) => otw?.isTaskCompleted(slugOrId) ?? false,\n [otw],\n )\n\n return (\n <OnTheWayContext.Provider\n value={{\n otw,\n ready,\n start,\n reset,\n resetAll,\n registerCondition,\n checkConditions,\n isTaskCompleted,\n }}\n >\n {children}\n </OnTheWayContext.Provider>\n )\n}\n\n// ---- Hook ----\n\n/**\n * Access the OnTheWay SDK from any component wrapped by `<OnTheWayProvider>`.\n *\n * @example\n * ```tsx\n * function HelpButton() {\n * const { start } = useOnTheWay()\n * return <button onClick={() => start('welcome')}>Help</button>\n * }\n * ```\n */\nexport function useOnTheWay() {\n return useContext(OnTheWayContext)\n}\n"]}