@eventop/sdk 1.2.2 → 1.2.11

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 CHANGED
@@ -52,12 +52,16 @@ function useFeatureScope() {
52
52
  *
53
53
  * In practice most flows are flat (index 0, 1, 2, 3...) but the
54
54
  * registry supports nested depth for complex interactions.
55
+ *
56
+ * Route awareness:
57
+ * Features can declare the pathname they live on via `route`.
58
+ * The snapshot() includes this so the SDK core can navigate
59
+ * automatically when a tour step targets a feature on a different page.
55
60
  */
56
61
  function createFeatureRegistry() {
57
62
  // Map<featureId, featureData>
58
63
  const features = new Map();
59
64
  // Map<featureId, Map<stepKey, stepData>>
60
- // stepKey is either a number (flat) or "parentIndex.childIndex" (nested)
61
65
  const flowSteps = new Map();
62
66
  const listeners = new Set();
63
67
  function notify() {
@@ -74,21 +78,19 @@ function createFeatureRegistry() {
74
78
  notify();
75
79
  }
76
80
  function unregisterFeature(id) {
77
- features.delete(id);
81
+ features.set(id, {
82
+ ...existing,
83
+ selector: null,
84
+ advanceOn: null,
85
+ waitFor: null,
86
+ _ghost: true
87
+ });
78
88
  flowSteps.delete(id);
79
89
  notify();
80
90
  }
81
91
 
82
92
  // ── Step registration ────────────────────────────────────────────────────
83
93
 
84
- /**
85
- * Register a flow step.
86
- *
87
- * @param {string} featureId - Parent feature id
88
- * @param {number} index - Position in the flat flow (0, 1, 2…)
89
- * @param {number|null} parentStep - If set, this is a sub-step of parentStep
90
- * @param {object} stepData - { selector, waitFor, advanceOn, ... }
91
- */
92
94
  function registerStep(featureId, index, parentStep, stepData) {
93
95
  if (!flowSteps.has(featureId)) {
94
96
  flowSteps.set(featureId, new Map());
@@ -113,35 +115,14 @@ function createFeatureRegistry() {
113
115
 
114
116
  // ── Snapshot ─────────────────────────────────────────────────────────────
115
117
 
116
- /**
117
- * Build the flat flow array the SDK core expects.
118
- *
119
- * For nested steps we inline sub-steps after their parent step,
120
- * in index order. This means a flow like:
121
- *
122
- * Step 0 (flat)
123
- * Step 1 (flat)
124
- * Step 1.0 (sub-step of 1)
125
- * Step 1.1 (sub-step of 1)
126
- * Step 2 (flat)
127
- *
128
- * becomes the SDK flow: [step0, step1, step1.0, step1.1, step2]
129
- *
130
- * The AI writes titles/text for the parent feature.
131
- * Sub-steps inherit the parent step's text with a "(N/M)" suffix added
132
- * by the SDK's expandFlowSteps() function.
133
- */
134
118
  function buildFlow(featureId) {
135
119
  const map = flowSteps.get(featureId);
136
120
  if (!map || map.size === 0) return null;
137
121
  const allSteps = Array.from(map.values());
138
-
139
- // Separate top-level and nested steps
140
122
  const topLevel = allSteps.filter(s => s.parentStep == null).sort((a, b) => a.index - b.index);
141
123
  const result = [];
142
124
  topLevel.forEach(step => {
143
125
  result.push(step);
144
- // Inline any sub-steps after the parent
145
126
  const children = allSteps.filter(s => s.parentStep === step.index).sort((a, b) => a.index - b.index);
146
127
  result.push(...children);
147
128
  });
@@ -150,7 +131,9 @@ function createFeatureRegistry() {
150
131
 
151
132
  /**
152
133
  * Returns the live feature array in the format the SDK core expects.
153
- * screen.check() is automatic — mounted = available.
134
+ *
135
+ * `route` is now included in every feature entry so the core can detect
136
+ * when navigation is needed before showing a step.
154
137
  */
155
138
  function snapshot() {
156
139
  return Array.from(features.values()).map(feature => {
@@ -160,6 +143,7 @@ function createFeatureRegistry() {
160
143
  name: feature.name,
161
144
  description: feature.description,
162
145
  selector: feature.selector,
146
+ route: feature.route || null,
163
147
  advanceOn: feature.advanceOn || null,
164
148
  waitFor: feature.waitFor || null,
165
149
  ...(flow ? {
@@ -195,7 +179,8 @@ function EventopProvider({
195
179
  assistantName,
196
180
  suggestions,
197
181
  theme,
198
- position
182
+ position,
183
+ router
199
184
  }) {
200
185
  if (!provider) throw new Error('[Eventop] <EventopProvider> requires a provider prop.');
201
186
  if (!appName) throw new Error('[Eventop] <EventopProvider> requires an appName prop.');
@@ -209,10 +194,9 @@ function EventopProvider({
209
194
  });
210
195
  }, [registry]);
211
196
  react.useEffect(() => {
212
- // Dynamically import core.js only in the browser
213
197
  async function boot() {
214
- // Import the core SDK (this only runs on client)
215
- await Promise.resolve().then(function () { return require('./core.cjs'); }).then(function (n) { return n.core; });
198
+ await Promise.resolve().then(function () { return require('./core.cjs'); }); // Ensure the UMD bundle is loaded (for Shepherd and global Eventop)
199
+
216
200
  window.Eventop.init({
217
201
  provider,
218
202
  config: {
@@ -221,6 +205,7 @@ function EventopProvider({
221
205
  suggestions,
222
206
  theme,
223
207
  position,
208
+ router,
224
209
  features: registry.snapshot(),
225
210
  _providerName: 'custom'
226
211
  }
@@ -238,6 +223,22 @@ function EventopProvider({
238
223
  }
239
224
  };
240
225
  }, [provider, appName, assistantName, suggestions, theme, position, registry, syncToSDK]);
226
+ // Note: `router` is intentionally omitted from the deps array above.
227
+ // Router instances from useNavigate / useRouter are stable references —
228
+ // including them would re-boot the SDK on every render.
229
+ // Instead, we sync router updates via a separate effect below.
230
+
231
+ // ── Keep the router reference fresh without re-booting the SDK ─────────────
232
+ // When the router instance changes (rare, but can happen in Next.js during
233
+ // hydration), we push the new reference into the already-running SDK core.
234
+ react.useEffect(() => {
235
+ if (sdkReady.current && window.Eventop) {
236
+ var _window$Eventop$_upda2, _window$Eventop3;
237
+ (_window$Eventop$_upda2 = (_window$Eventop3 = window.Eventop)._updateConfig) === null || _window$Eventop$_upda2 === void 0 || _window$Eventop$_upda2.call(_window$Eventop3, {
238
+ router
239
+ });
240
+ }
241
+ }, [router]);
241
242
  const ctx = {
242
243
  registerFeature: registry.registerFeature,
243
244
  unregisterFeature: registry.unregisterFeature,
@@ -256,6 +257,7 @@ function EventopTarget({
256
257
  id,
257
258
  name,
258
259
  description,
260
+ route,
259
261
  navigate,
260
262
  navigateWaitFor,
261
263
  advanceOn,
@@ -275,6 +277,7 @@ function EventopTarget({
275
277
  id,
276
278
  name,
277
279
  description,
280
+ route,
278
281
  selector,
279
282
  navigate,
280
283
  navigateWaitFor,
@@ -285,7 +288,7 @@ function EventopTarget({
285
288
  } : null
286
289
  });
287
290
  return () => registry.unregisterFeature(id);
288
- }, [id, name, description]);
291
+ }, [id, name, description, route]);
289
292
  const child = react.Children.only(children);
290
293
  let wrapped;
291
294
  try {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // ─── Step ────────────────────────────────────────────────────────────────────
2
2
 
3
+ import { JSX } from "react";
4
+
3
5
  export interface Step {
4
6
  id?: string;
5
7
  title: string;
@@ -54,6 +56,19 @@ export interface Feature {
54
56
  description?: string;
55
57
  /** CSS selector for the feature's primary DOM element */
56
58
  selector: string;
59
+ /**
60
+ * The pathname where this feature lives (e.g. "/settings/billing").
61
+ *
62
+ * When a tour step targets this feature and the user is on a different
63
+ * page, the SDK will:
64
+ * 1. Tell the user it's navigating and why
65
+ * 2. Call the `router` function passed to EventopAIProvider / init()
66
+ * 3. Wait for the feature element to appear before showing the step
67
+ *
68
+ * Prefer `route` over the legacy `screen` API for React Router and
69
+ * Next.js apps.
70
+ */
71
+ route?: string;
57
72
  /** Additional related selectors for context */
58
73
  relatedSelectors?: Record<string, string>;
59
74
  /** Auto-advance when this event fires */
@@ -67,7 +82,8 @@ export interface Feature {
67
82
  flow?: FlowStep[];
68
83
  /**
69
84
  * Screen this feature lives on.
70
- * SDK navigates to the correct screen before showing the step.
85
+ * @deprecated Prefer `route` + the `router` prop on EventopAIProvider.
86
+ * `screen` is still supported for backward compatibility.
71
87
  */
72
88
  screen?: Screen;
73
89
  }
@@ -119,6 +135,27 @@ export interface Config {
119
135
  suggestions?: string[];
120
136
  theme?: Theme;
121
137
  position?: Position;
138
+ /**
139
+ * Navigation function the SDK calls when a tour step lives on a different page.
140
+ *
141
+ * Pass your framework's navigate/push function here:
142
+ *
143
+ * React Router v6:
144
+ * const navigate = useNavigate();
145
+ * <EventopAIProvider router={navigate} ...>
146
+ *
147
+ * Next.js App Router:
148
+ * const router = useRouter();
149
+ * <EventopAIProvider router={(path) => router.push(path)} ...>
150
+ *
151
+ * Next.js Pages Router:
152
+ * const router = useRouter();
153
+ * <EventopAIProvider router={(path) => router.push(path)} ...>
154
+ *
155
+ * If omitted, the SDK falls back to window.history.pushState + popstate
156
+ * (best-effort; works for simple SPAs that listen to popstate).
157
+ */
158
+ router?: (path: string) => void | Promise<void>;
122
159
  /** @internal — set by provider factories */
123
160
  _providerName?: string;
124
161
  }
@@ -174,16 +211,6 @@ export interface Providers {
174
211
  /**
175
212
  * Custom provider — proxy through your own server.
176
213
  * Recommended for production. Keeps API keys off the browser.
177
- *
178
- * @example
179
- * providers.custom(async ({ systemPrompt, messages }) => {
180
- * const res = await fetch('/api/guide', {
181
- * method: 'POST',
182
- * headers: { 'Content-Type': 'application/json' },
183
- * body: JSON.stringify({ systemPrompt, messages }),
184
- * });
185
- * return res.json();
186
- * })
187
214
  */
188
215
  custom(fn: ProviderFn): ProviderFn;
189
216
  }
@@ -215,40 +242,22 @@ export interface EventopSDK {
215
242
  */
216
243
  runTour(steps: Step[], options?: { showProgress?: boolean; waitTimeout?: number }): Promise<void>;
217
244
 
218
- /**
219
- * Hard cancel the active tour.
220
- * Clears all pause state — no resume available after this.
221
- */
245
+ /** Hard cancel the active tour. */
222
246
  cancelTour(): void;
223
247
 
224
- /**
225
- * Resume a paused tour from where the user left off.
226
- * Called automatically by the resume button in the chat panel.
227
- */
248
+ /** Resume a paused tour from where the user left off. */
228
249
  resumeTour(): void;
229
250
 
230
- /**
231
- * Advance the current tour step programmatically.
232
- * Call after async validation succeeds.
233
- *
234
- * @example
235
- * const ok = await validateEmail(email);
236
- * if (ok) EventopAI.stepComplete();
237
- */
251
+ /** Advance the current tour step programmatically. */
238
252
  stepComplete(): void;
239
253
 
240
- /**
241
- * Block tour advancement and show an inline error in the current step tooltip.
242
- *
243
- * @example
244
- * EventopAI.stepFail('Please enter a valid email address.');
245
- */
254
+ /** Block tour advancement and show an inline error. */
246
255
  stepFail(message: string): void;
247
256
 
248
257
  /** Returns true if a tour is currently running */
249
258
  isActive(): boolean;
250
259
 
251
- /** Returns true if a tour is paused (cancelled but resumable) */
260
+ /** Returns true if a tour is paused */
252
261
  isPaused(): boolean;
253
262
 
254
263
  /** @internal — used by the React/Vue packages to sync live feature registry */
@@ -256,4 +265,78 @@ export interface EventopSDK {
256
265
  }
257
266
 
258
267
  declare const EventopAI: EventopSDK;
259
- export default EventopAI;
268
+ export default EventopAI;
269
+
270
+
271
+ // ─── React bindings ───────────────────────────────────────────────────────────
272
+
273
+ export interface EventopAIProviderProps {
274
+ children: React.ReactNode;
275
+ provider: ProviderFn;
276
+ appName: string;
277
+ assistantName?: string;
278
+ suggestions?: string[];
279
+ theme?: Theme;
280
+ position?: Position;
281
+ /**
282
+ * Navigation function for cross-page tours.
283
+ *
284
+ * React Router v6: pass `useNavigate()` directly
285
+ * Next.js App Router: pass `(path) => useRouter().push(path)`
286
+ * Next.js Pages Router: pass `(path) => useRouter().push(path)`
287
+ */
288
+ router?: (path: string) => void | Promise<void>;
289
+ }
290
+
291
+ export interface EventopTargetProps {
292
+ children: React.ReactElement;
293
+ id: string;
294
+ name: string;
295
+ description?: string;
296
+ /**
297
+ * The pathname where this feature lives (e.g. "/settings/billing").
298
+ * The SDK auto-navigates here when a tour step targets this feature
299
+ * and the user is on a different page.
300
+ */
301
+ route?: string;
302
+ navigate?: () => void | Promise<void>;
303
+ navigateWaitFor?: string;
304
+ advanceOn?: Omit<AdvanceOn, 'selector'>;
305
+ waitFor?: string;
306
+ }
307
+
308
+ export interface EventopStepProps {
309
+ children: React.ReactElement;
310
+ feature?: string;
311
+ index: number;
312
+ parentStep?: number;
313
+ waitFor?: string;
314
+ advanceOn?: Omit<AdvanceOn, 'selector'>;
315
+ }
316
+
317
+ export interface UseEventopAIReturn {
318
+ open(): void;
319
+ close(): void;
320
+ cancelTour(): void;
321
+ resumeTour(): void;
322
+ isActive(): boolean;
323
+ isPaused(): boolean;
324
+ stepComplete(): void;
325
+ stepFail(msg: string): void;
326
+ runTour(steps: Step[]): Promise<void>;
327
+ }
328
+
329
+ export interface UseEventopTourReturn {
330
+ isActive: boolean;
331
+ isPaused: boolean;
332
+ resume(): void;
333
+ cancel(): void;
334
+ open(): void;
335
+ close(): void;
336
+ }
337
+
338
+ export declare function EventopAIProvider(props: EventopAIProviderProps): JSX.Element;
339
+ export declare function EventopTarget(props: EventopTargetProps): JSX.Element;
340
+ export declare function EventopStep(props: EventopStepProps): JSX.Element;
341
+ export declare function useEventopAI(): UseEventopAIReturn;
342
+ export declare function useEventopTour(): UseEventopTourReturn;
package/dist/index.js CHANGED
@@ -50,12 +50,16 @@ function useFeatureScope() {
50
50
  *
51
51
  * In practice most flows are flat (index 0, 1, 2, 3...) but the
52
52
  * registry supports nested depth for complex interactions.
53
+ *
54
+ * Route awareness:
55
+ * Features can declare the pathname they live on via `route`.
56
+ * The snapshot() includes this so the SDK core can navigate
57
+ * automatically when a tour step targets a feature on a different page.
53
58
  */
54
59
  function createFeatureRegistry() {
55
60
  // Map<featureId, featureData>
56
61
  const features = new Map();
57
62
  // Map<featureId, Map<stepKey, stepData>>
58
- // stepKey is either a number (flat) or "parentIndex.childIndex" (nested)
59
63
  const flowSteps = new Map();
60
64
  const listeners = new Set();
61
65
  function notify() {
@@ -72,21 +76,19 @@ function createFeatureRegistry() {
72
76
  notify();
73
77
  }
74
78
  function unregisterFeature(id) {
75
- features.delete(id);
79
+ features.set(id, {
80
+ ...existing,
81
+ selector: null,
82
+ advanceOn: null,
83
+ waitFor: null,
84
+ _ghost: true
85
+ });
76
86
  flowSteps.delete(id);
77
87
  notify();
78
88
  }
79
89
 
80
90
  // ── Step registration ────────────────────────────────────────────────────
81
91
 
82
- /**
83
- * Register a flow step.
84
- *
85
- * @param {string} featureId - Parent feature id
86
- * @param {number} index - Position in the flat flow (0, 1, 2…)
87
- * @param {number|null} parentStep - If set, this is a sub-step of parentStep
88
- * @param {object} stepData - { selector, waitFor, advanceOn, ... }
89
- */
90
92
  function registerStep(featureId, index, parentStep, stepData) {
91
93
  if (!flowSteps.has(featureId)) {
92
94
  flowSteps.set(featureId, new Map());
@@ -111,35 +113,14 @@ function createFeatureRegistry() {
111
113
 
112
114
  // ── Snapshot ─────────────────────────────────────────────────────────────
113
115
 
114
- /**
115
- * Build the flat flow array the SDK core expects.
116
- *
117
- * For nested steps we inline sub-steps after their parent step,
118
- * in index order. This means a flow like:
119
- *
120
- * Step 0 (flat)
121
- * Step 1 (flat)
122
- * Step 1.0 (sub-step of 1)
123
- * Step 1.1 (sub-step of 1)
124
- * Step 2 (flat)
125
- *
126
- * becomes the SDK flow: [step0, step1, step1.0, step1.1, step2]
127
- *
128
- * The AI writes titles/text for the parent feature.
129
- * Sub-steps inherit the parent step's text with a "(N/M)" suffix added
130
- * by the SDK's expandFlowSteps() function.
131
- */
132
116
  function buildFlow(featureId) {
133
117
  const map = flowSteps.get(featureId);
134
118
  if (!map || map.size === 0) return null;
135
119
  const allSteps = Array.from(map.values());
136
-
137
- // Separate top-level and nested steps
138
120
  const topLevel = allSteps.filter(s => s.parentStep == null).sort((a, b) => a.index - b.index);
139
121
  const result = [];
140
122
  topLevel.forEach(step => {
141
123
  result.push(step);
142
- // Inline any sub-steps after the parent
143
124
  const children = allSteps.filter(s => s.parentStep === step.index).sort((a, b) => a.index - b.index);
144
125
  result.push(...children);
145
126
  });
@@ -148,7 +129,9 @@ function createFeatureRegistry() {
148
129
 
149
130
  /**
150
131
  * Returns the live feature array in the format the SDK core expects.
151
- * screen.check() is automatic — mounted = available.
132
+ *
133
+ * `route` is now included in every feature entry so the core can detect
134
+ * when navigation is needed before showing a step.
152
135
  */
153
136
  function snapshot() {
154
137
  return Array.from(features.values()).map(feature => {
@@ -158,6 +141,7 @@ function createFeatureRegistry() {
158
141
  name: feature.name,
159
142
  description: feature.description,
160
143
  selector: feature.selector,
144
+ route: feature.route || null,
161
145
  advanceOn: feature.advanceOn || null,
162
146
  waitFor: feature.waitFor || null,
163
147
  ...(flow ? {
@@ -193,7 +177,8 @@ function EventopProvider({
193
177
  assistantName,
194
178
  suggestions,
195
179
  theme,
196
- position
180
+ position,
181
+ router
197
182
  }) {
198
183
  if (!provider) throw new Error('[Eventop] <EventopProvider> requires a provider prop.');
199
184
  if (!appName) throw new Error('[Eventop] <EventopProvider> requires an appName prop.');
@@ -207,10 +192,9 @@ function EventopProvider({
207
192
  });
208
193
  }, [registry]);
209
194
  useEffect(() => {
210
- // Dynamically import core.js only in the browser
211
195
  async function boot() {
212
- // Import the core SDK (this only runs on client)
213
- await import('./core.js').then(function (n) { return n.c; });
196
+ await import('./core.js'); // Ensure the UMD bundle is loaded (for Shepherd and global Eventop)
197
+
214
198
  window.Eventop.init({
215
199
  provider,
216
200
  config: {
@@ -219,6 +203,7 @@ function EventopProvider({
219
203
  suggestions,
220
204
  theme,
221
205
  position,
206
+ router,
222
207
  features: registry.snapshot(),
223
208
  _providerName: 'custom'
224
209
  }
@@ -236,6 +221,22 @@ function EventopProvider({
236
221
  }
237
222
  };
238
223
  }, [provider, appName, assistantName, suggestions, theme, position, registry, syncToSDK]);
224
+ // Note: `router` is intentionally omitted from the deps array above.
225
+ // Router instances from useNavigate / useRouter are stable references —
226
+ // including them would re-boot the SDK on every render.
227
+ // Instead, we sync router updates via a separate effect below.
228
+
229
+ // ── Keep the router reference fresh without re-booting the SDK ─────────────
230
+ // When the router instance changes (rare, but can happen in Next.js during
231
+ // hydration), we push the new reference into the already-running SDK core.
232
+ useEffect(() => {
233
+ if (sdkReady.current && window.Eventop) {
234
+ var _window$Eventop$_upda2, _window$Eventop3;
235
+ (_window$Eventop$_upda2 = (_window$Eventop3 = window.Eventop)._updateConfig) === null || _window$Eventop$_upda2 === void 0 || _window$Eventop$_upda2.call(_window$Eventop3, {
236
+ router
237
+ });
238
+ }
239
+ }, [router]);
239
240
  const ctx = {
240
241
  registerFeature: registry.registerFeature,
241
242
  unregisterFeature: registry.unregisterFeature,
@@ -254,6 +255,7 @@ function EventopTarget({
254
255
  id,
255
256
  name,
256
257
  description,
258
+ route,
257
259
  navigate,
258
260
  navigateWaitFor,
259
261
  advanceOn,
@@ -273,6 +275,7 @@ function EventopTarget({
273
275
  id,
274
276
  name,
275
277
  description,
278
+ route,
276
279
  selector,
277
280
  navigate,
278
281
  navigateWaitFor,
@@ -283,7 +286,7 @@ function EventopTarget({
283
286
  } : null
284
287
  });
285
288
  return () => registry.unregisterFeature(id);
286
- }, [id, name, description]);
289
+ }, [id, name, description, route]);
287
290
  const child = Children.only(children);
288
291
  let wrapped;
289
292
  try {