@eventop/sdk 1.2.2 → 1.2.10

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() {
@@ -81,14 +85,6 @@ function createFeatureRegistry() {
81
85
 
82
86
  // ── Step registration ────────────────────────────────────────────────────
83
87
 
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
88
  function registerStep(featureId, index, parentStep, stepData) {
93
89
  if (!flowSteps.has(featureId)) {
94
90
  flowSteps.set(featureId, new Map());
@@ -113,35 +109,14 @@ function createFeatureRegistry() {
113
109
 
114
110
  // ── Snapshot ─────────────────────────────────────────────────────────────
115
111
 
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
112
  function buildFlow(featureId) {
135
113
  const map = flowSteps.get(featureId);
136
114
  if (!map || map.size === 0) return null;
137
115
  const allSteps = Array.from(map.values());
138
-
139
- // Separate top-level and nested steps
140
116
  const topLevel = allSteps.filter(s => s.parentStep == null).sort((a, b) => a.index - b.index);
141
117
  const result = [];
142
118
  topLevel.forEach(step => {
143
119
  result.push(step);
144
- // Inline any sub-steps after the parent
145
120
  const children = allSteps.filter(s => s.parentStep === step.index).sort((a, b) => a.index - b.index);
146
121
  result.push(...children);
147
122
  });
@@ -150,7 +125,9 @@ function createFeatureRegistry() {
150
125
 
151
126
  /**
152
127
  * Returns the live feature array in the format the SDK core expects.
153
- * screen.check() is automatic — mounted = available.
128
+ *
129
+ * `route` is now included in every feature entry so the core can detect
130
+ * when navigation is needed before showing a step.
154
131
  */
155
132
  function snapshot() {
156
133
  return Array.from(features.values()).map(feature => {
@@ -160,6 +137,7 @@ function createFeatureRegistry() {
160
137
  name: feature.name,
161
138
  description: feature.description,
162
139
  selector: feature.selector,
140
+ route: feature.route || null,
163
141
  advanceOn: feature.advanceOn || null,
164
142
  waitFor: feature.waitFor || null,
165
143
  ...(flow ? {
@@ -195,7 +173,8 @@ function EventopProvider({
195
173
  assistantName,
196
174
  suggestions,
197
175
  theme,
198
- position
176
+ position,
177
+ router
199
178
  }) {
200
179
  if (!provider) throw new Error('[Eventop] <EventopProvider> requires a provider prop.');
201
180
  if (!appName) throw new Error('[Eventop] <EventopProvider> requires an appName prop.');
@@ -209,10 +188,9 @@ function EventopProvider({
209
188
  });
210
189
  }, [registry]);
211
190
  react.useEffect(() => {
212
- // Dynamically import core.js only in the browser
213
191
  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; });
192
+ await Promise.resolve().then(function () { return require('./core.cjs'); }); // Ensure the UMD bundle is loaded (for Shepherd and global Eventop)
193
+
216
194
  window.Eventop.init({
217
195
  provider,
218
196
  config: {
@@ -221,6 +199,7 @@ function EventopProvider({
221
199
  suggestions,
222
200
  theme,
223
201
  position,
202
+ router,
224
203
  features: registry.snapshot(),
225
204
  _providerName: 'custom'
226
205
  }
@@ -238,6 +217,22 @@ function EventopProvider({
238
217
  }
239
218
  };
240
219
  }, [provider, appName, assistantName, suggestions, theme, position, registry, syncToSDK]);
220
+ // Note: `router` is intentionally omitted from the deps array above.
221
+ // Router instances from useNavigate / useRouter are stable references —
222
+ // including them would re-boot the SDK on every render.
223
+ // Instead, we sync router updates via a separate effect below.
224
+
225
+ // ── Keep the router reference fresh without re-booting the SDK ─────────────
226
+ // When the router instance changes (rare, but can happen in Next.js during
227
+ // hydration), we push the new reference into the already-running SDK core.
228
+ react.useEffect(() => {
229
+ if (sdkReady.current && window.Eventop) {
230
+ var _window$Eventop$_upda2, _window$Eventop3;
231
+ (_window$Eventop$_upda2 = (_window$Eventop3 = window.Eventop)._updateConfig) === null || _window$Eventop$_upda2 === void 0 || _window$Eventop$_upda2.call(_window$Eventop3, {
232
+ router
233
+ });
234
+ }
235
+ }, [router]);
241
236
  const ctx = {
242
237
  registerFeature: registry.registerFeature,
243
238
  unregisterFeature: registry.unregisterFeature,
@@ -256,6 +251,7 @@ function EventopTarget({
256
251
  id,
257
252
  name,
258
253
  description,
254
+ route,
259
255
  navigate,
260
256
  navigateWaitFor,
261
257
  advanceOn,
@@ -275,6 +271,7 @@ function EventopTarget({
275
271
  id,
276
272
  name,
277
273
  description,
274
+ route,
278
275
  selector,
279
276
  navigate,
280
277
  navigateWaitFor,
@@ -285,7 +282,7 @@ function EventopTarget({
285
282
  } : null
286
283
  });
287
284
  return () => registry.unregisterFeature(id);
288
- }, [id, name, description]);
285
+ }, [id, name, description, route]);
289
286
  const child = react.Children.only(children);
290
287
  let wrapped;
291
288
  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() {
@@ -79,14 +83,6 @@ function createFeatureRegistry() {
79
83
 
80
84
  // ── Step registration ────────────────────────────────────────────────────
81
85
 
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
86
  function registerStep(featureId, index, parentStep, stepData) {
91
87
  if (!flowSteps.has(featureId)) {
92
88
  flowSteps.set(featureId, new Map());
@@ -111,35 +107,14 @@ function createFeatureRegistry() {
111
107
 
112
108
  // ── Snapshot ─────────────────────────────────────────────────────────────
113
109
 
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
110
  function buildFlow(featureId) {
133
111
  const map = flowSteps.get(featureId);
134
112
  if (!map || map.size === 0) return null;
135
113
  const allSteps = Array.from(map.values());
136
-
137
- // Separate top-level and nested steps
138
114
  const topLevel = allSteps.filter(s => s.parentStep == null).sort((a, b) => a.index - b.index);
139
115
  const result = [];
140
116
  topLevel.forEach(step => {
141
117
  result.push(step);
142
- // Inline any sub-steps after the parent
143
118
  const children = allSteps.filter(s => s.parentStep === step.index).sort((a, b) => a.index - b.index);
144
119
  result.push(...children);
145
120
  });
@@ -148,7 +123,9 @@ function createFeatureRegistry() {
148
123
 
149
124
  /**
150
125
  * Returns the live feature array in the format the SDK core expects.
151
- * screen.check() is automatic — mounted = available.
126
+ *
127
+ * `route` is now included in every feature entry so the core can detect
128
+ * when navigation is needed before showing a step.
152
129
  */
153
130
  function snapshot() {
154
131
  return Array.from(features.values()).map(feature => {
@@ -158,6 +135,7 @@ function createFeatureRegistry() {
158
135
  name: feature.name,
159
136
  description: feature.description,
160
137
  selector: feature.selector,
138
+ route: feature.route || null,
161
139
  advanceOn: feature.advanceOn || null,
162
140
  waitFor: feature.waitFor || null,
163
141
  ...(flow ? {
@@ -193,7 +171,8 @@ function EventopProvider({
193
171
  assistantName,
194
172
  suggestions,
195
173
  theme,
196
- position
174
+ position,
175
+ router
197
176
  }) {
198
177
  if (!provider) throw new Error('[Eventop] <EventopProvider> requires a provider prop.');
199
178
  if (!appName) throw new Error('[Eventop] <EventopProvider> requires an appName prop.');
@@ -207,10 +186,9 @@ function EventopProvider({
207
186
  });
208
187
  }, [registry]);
209
188
  useEffect(() => {
210
- // Dynamically import core.js only in the browser
211
189
  async function boot() {
212
- // Import the core SDK (this only runs on client)
213
- await import('./core.js').then(function (n) { return n.c; });
190
+ await import('./core.js'); // Ensure the UMD bundle is loaded (for Shepherd and global Eventop)
191
+
214
192
  window.Eventop.init({
215
193
  provider,
216
194
  config: {
@@ -219,6 +197,7 @@ function EventopProvider({
219
197
  suggestions,
220
198
  theme,
221
199
  position,
200
+ router,
222
201
  features: registry.snapshot(),
223
202
  _providerName: 'custom'
224
203
  }
@@ -236,6 +215,22 @@ function EventopProvider({
236
215
  }
237
216
  };
238
217
  }, [provider, appName, assistantName, suggestions, theme, position, registry, syncToSDK]);
218
+ // Note: `router` is intentionally omitted from the deps array above.
219
+ // Router instances from useNavigate / useRouter are stable references —
220
+ // including them would re-boot the SDK on every render.
221
+ // Instead, we sync router updates via a separate effect below.
222
+
223
+ // ── Keep the router reference fresh without re-booting the SDK ─────────────
224
+ // When the router instance changes (rare, but can happen in Next.js during
225
+ // hydration), we push the new reference into the already-running SDK core.
226
+ useEffect(() => {
227
+ if (sdkReady.current && window.Eventop) {
228
+ var _window$Eventop$_upda2, _window$Eventop3;
229
+ (_window$Eventop$_upda2 = (_window$Eventop3 = window.Eventop)._updateConfig) === null || _window$Eventop$_upda2 === void 0 || _window$Eventop$_upda2.call(_window$Eventop3, {
230
+ router
231
+ });
232
+ }
233
+ }, [router]);
239
234
  const ctx = {
240
235
  registerFeature: registry.registerFeature,
241
236
  unregisterFeature: registry.unregisterFeature,
@@ -254,6 +249,7 @@ function EventopTarget({
254
249
  id,
255
250
  name,
256
251
  description,
252
+ route,
257
253
  navigate,
258
254
  navigateWaitFor,
259
255
  advanceOn,
@@ -273,6 +269,7 @@ function EventopTarget({
273
269
  id,
274
270
  name,
275
271
  description,
272
+ route,
276
273
  selector,
277
274
  navigate,
278
275
  navigateWaitFor,
@@ -283,7 +280,7 @@ function EventopTarget({
283
280
  } : null
284
281
  });
285
282
  return () => registry.unregisterFeature(id);
286
- }, [id, name, description]);
283
+ }, [id, name, description, route]);
287
284
  const child = Children.only(children);
288
285
  let wrapped;
289
286
  try {