@aladinbs/react-guided-tour 1.0.2 → 1.0.3

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 (39) hide show
  1. package/README.md +11 -1
  2. package/dist/components/ErrorBoundary.d.ts +31 -0
  3. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  4. package/dist/components/Toast.d.ts +14 -0
  5. package/dist/components/Toast.d.ts.map +1 -0
  6. package/dist/components/ToastProvider.d.ts +28 -0
  7. package/dist/components/ToastProvider.d.ts.map +1 -0
  8. package/dist/components/TourOverlay.d.ts +2 -1
  9. package/dist/components/TourOverlay.d.ts.map +1 -1
  10. package/dist/components/TourPopover.d.ts +2 -1
  11. package/dist/components/TourPopover.d.ts.map +1 -1
  12. package/dist/components/TourProvider.d.ts +10 -2
  13. package/dist/components/TourProvider.d.ts.map +1 -1
  14. package/dist/components/TourRunner.d.ts +2 -1
  15. package/dist/components/TourRunner.d.ts.map +1 -1
  16. package/dist/core/TourActions.d.ts +10 -0
  17. package/dist/core/TourActions.d.ts.map +1 -1
  18. package/dist/core/TourEngine.d.ts +32 -0
  19. package/dist/core/TourEngine.d.ts.map +1 -1
  20. package/dist/hooks/useErrorHandler.d.ts +26 -0
  21. package/dist/hooks/useErrorHandler.d.ts.map +1 -0
  22. package/dist/hooks/useToastErrorHandler.d.ts +15 -0
  23. package/dist/hooks/useToastErrorHandler.d.ts.map +1 -0
  24. package/dist/hooks/useTourEngine.d.ts +4 -0
  25. package/dist/hooks/useTourEngine.d.ts.map +1 -1
  26. package/dist/hooks/useTourHighlight.d.ts +4 -0
  27. package/dist/hooks/useTourHighlight.d.ts.map +1 -1
  28. package/dist/index.d.ts +136 -6
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.esm.js +418 -137
  31. package/dist/index.esm.js.map +1 -1
  32. package/dist/index.js +442 -156
  33. package/dist/index.js.map +1 -1
  34. package/dist/integrations/TabIntegration.d.ts.map +1 -1
  35. package/dist/types/index.d.ts +3 -0
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/utils/positioning.d.ts +13 -0
  38. package/dist/utils/positioning.d.ts.map +1 -1
  39. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import require$$0, { useMemo, useState, useEffect, useCallback, createContext, useContext, useRef } from 'react';
1
+ import React, { useRef, useCallback, useMemo, useState, useEffect, createContext, useContext, Component } from 'react';
2
2
 
3
3
  class TourStorage {
4
4
  constructor(key) {
@@ -95,6 +95,10 @@ class TourStorage {
95
95
  }
96
96
  }
97
97
 
98
+ /**
99
+ * Core tour engine that manages tour state, navigation, and lifecycle.
100
+ * Handles step progression, state persistence, and event emission.
101
+ */
98
102
  class TourEngine {
99
103
  constructor(config) {
100
104
  this.listeners = new Map();
@@ -125,6 +129,9 @@ class TourEngine {
125
129
  getConfig() {
126
130
  return { ...this.config };
127
131
  }
132
+ /**
133
+ * Starts the tour from the current step index.
134
+ */
128
135
  async start() {
129
136
  try {
130
137
  this.setState({
@@ -134,14 +141,22 @@ class TourEngine {
134
141
  });
135
142
  this.emit('tour-start', { tourId: this.config.id });
136
143
  await this.goToStep(this.state.currentStepIndex);
137
- // STOP HERE - don't auto-advance
138
144
  return;
139
145
  }
140
146
  catch (error) {
141
- console.error('Error in TourEngine.start():', error);
142
- this.handleError(error);
147
+ const errorObj = error;
148
+ console.error('Error in TourEngine.start():', errorObj);
149
+ this.handleError(errorObj);
150
+ this.setState({
151
+ isRunning: false,
152
+ isLoading: false
153
+ });
154
+ throw errorObj;
143
155
  }
144
156
  }
157
+ /**
158
+ * Advances to the next step in the tour.
159
+ */
145
160
  async next() {
146
161
  if (!this.state.isRunning) {
147
162
  return;
@@ -149,28 +164,34 @@ class TourEngine {
149
164
  try {
150
165
  const currentStep = this.getCurrentStep();
151
166
  if (!currentStep) {
152
- return;
167
+ throw new Error('No current step available for next operation');
153
168
  }
154
- // Execute after step hook
155
169
  if (currentStep.afterStep) {
156
- await currentStep.afterStep();
170
+ try {
171
+ await currentStep.afterStep();
172
+ }
173
+ catch (hookError) {
174
+ console.warn('Error in afterStep hook:', hookError);
175
+ }
157
176
  }
158
- // Mark step as completed
159
177
  this.markStepCompleted(currentStep.id);
160
- // Check if this is the last step
161
178
  if (this.state.currentStepIndex >= this.state.totalSteps - 1) {
162
179
  await this.complete();
163
180
  return;
164
181
  }
165
- // Go to next step
166
182
  const nextStepIndex = this.state.currentStepIndex + 1;
167
183
  await this.goToStep(nextStepIndex);
168
184
  }
169
185
  catch (error) {
170
- console.error('Error in TourEngine.next():', error);
171
- this.handleError(error);
186
+ const errorObj = error;
187
+ console.error('Error in TourEngine.next():', errorObj);
188
+ this.handleError(errorObj);
189
+ throw errorObj;
172
190
  }
173
191
  }
192
+ /**
193
+ * Goes back to the previous step in the tour.
194
+ */
174
195
  async previous() {
175
196
  if (!this.state.isRunning || this.state.currentStepIndex <= 0)
176
197
  return;
@@ -181,40 +202,65 @@ class TourEngine {
181
202
  this.handleError(error);
182
203
  }
183
204
  }
205
+ /**
206
+ * Navigates to a specific step by index.
207
+ */
184
208
  async goToStep(index) {
185
- if (index < 0 || index >= this.state.totalSteps)
186
- return;
209
+ if (index < 0 || index >= this.state.totalSteps) {
210
+ throw new Error(`Invalid step index: ${index}. Must be between 0 and ${this.state.totalSteps - 1}`);
211
+ }
187
212
  try {
188
213
  this.setState({ isLoading: true });
189
214
  const step = this.config.steps[index];
190
- // Execute before step hook
191
215
  if (step.beforeStep) {
192
- await step.beforeStep();
216
+ try {
217
+ await step.beforeStep();
218
+ }
219
+ catch (hookError) {
220
+ console.warn('Error in beforeStep hook:', hookError);
221
+ }
193
222
  }
194
- // Wait for element if specified
195
223
  if (step.waitForElement && step.target) {
196
- await this.waitForElement(step.target, step.waitTimeout || 5000);
224
+ try {
225
+ await this.waitForElement(step.target, step.waitTimeout || 5000);
226
+ }
227
+ catch (waitError) {
228
+ console.warn(`Element not found: ${step.target}. Continuing anyway.`);
229
+ }
197
230
  }
198
231
  this.setState({
199
232
  currentStepIndex: index,
200
233
  currentStep: step,
201
234
  isLoading: false,
202
235
  });
203
- // Save state if persistence is enabled
204
236
  if (this.config.storage?.remember) {
205
- this.storage.saveState(this.state);
237
+ try {
238
+ this.storage.saveState(this.state);
239
+ }
240
+ catch (storageError) {
241
+ console.warn('Failed to save tour state:', storageError);
242
+ }
206
243
  }
207
- // Emit step change event
208
244
  this.emit('step-change', { step, index });
209
245
  if (this.config.onStepChange) {
210
- this.config.onStepChange(step, index);
246
+ try {
247
+ this.config.onStepChange(step, index);
248
+ }
249
+ catch (callbackError) {
250
+ console.warn('Error in onStepChange callback:', callbackError);
251
+ }
211
252
  }
212
253
  }
213
254
  catch (error) {
214
255
  this.setState({ isLoading: false });
215
- this.handleError(error);
256
+ const errorObj = error;
257
+ this.handleError(errorObj);
258
+ throw errorObj;
216
259
  }
217
260
  }
261
+ /**
262
+ * Skips the current tour and marks it as skipped.
263
+ */
218
264
  async skip() {
219
265
  if (!this.state.isRunning)
220
266
  return;
@@ -228,7 +274,6 @@ class TourEngine {
228
274
  if (this.config.onSkip) {
229
275
  this.config.onSkip();
230
276
  }
231
- // Save skip state to localStorage
232
277
  if (this.config.storage?.remember) {
233
278
  this.storage.saveSkip();
234
279
  }
@@ -239,6 +284,9 @@ class TourEngine {
239
284
  this.handleError(error);
240
285
  }
241
286
  }
287
+ /**
288
+ * Completes the tour and marks it as finished.
289
+ */
242
290
  async complete() {
243
291
  try {
244
292
  this.setState({ isRunning: false, isCompleted: true });
@@ -246,7 +294,6 @@ class TourEngine {
246
294
  if (this.config.onComplete) {
247
295
  this.config.onComplete();
248
296
  }
249
- // Save completion state to localStorage
250
297
  if (this.config.storage?.remember) {
251
298
  this.storage.saveCompletion();
252
299
  }
@@ -277,11 +324,17 @@ class TourEngine {
277
324
  canGoPrevious() {
278
325
  return this.state.isRunning && !this.isFirstStep() && !this.state.isLoading;
279
326
  }
327
+ /**
328
+ * Determines if the tour should be shown based on completion/skip state.
329
+ */
280
330
  shouldShowTour() {
281
331
  if (!this.config.storage?.remember)
282
332
  return true;
283
333
  return !this.storage.isCompleted() && !this.storage.isSkipped();
284
334
  }
335
+ /**
336
+ * Resets tour state and clears localStorage.
337
+ */
285
338
  resetTourState() {
286
339
  if (this.config.storage?.remember) {
287
340
  this.storage.clearState();
@@ -328,18 +381,32 @@ class TourEngine {
328
381
  }
329
382
  }
330
383
  handleError(error) {
384
+ const errorMessage = error.message || 'Unknown error occurred';
385
+ const currentStep = this.getCurrentStep();
331
386
  this.setState({
332
- error: error.message,
387
+ error: errorMessage,
333
388
  isLoading: false,
334
389
  });
335
- this.emit('error', { error, step: this.getCurrentStep() || undefined });
390
+ this.emit('error', {
391
+ error,
392
+ step: currentStep || undefined,
393
+ tourId: this.config.id,
394
+ stepIndex: this.state.currentStepIndex,
395
+ timestamp: new Date().toISOString()
396
+ });
397
+ console.error('TourEngine Error Details:', {
398
+ message: errorMessage,
399
+ stack: error.stack,
400
+ tourId: this.config.id,
401
+ currentStep: currentStep?.id,
402
+ stepIndex: this.state.currentStepIndex,
403
+ state: this.state
404
+ });
336
405
  }
337
406
  setState(updates) {
338
407
  this.state = { ...this.state, ...updates };
339
- // Emit state change event to notify subscribers
340
408
  this.emit('state-change', this.state);
341
409
  }
342
- // Event system
343
410
  on(event, callback) {
344
411
  if (!this.listeners.has(event)) {
345
412
  this.listeners.set(event, new Set());
@@ -358,16 +425,18 @@ class TourEngine {
358
425
  callbacks.forEach(callback => callback(data));
359
426
  }
360
427
  }
428
+ /**
429
+ * Subscribes to tour state changes.
430
+ * Returns an unsubscribe function.
431
+ */
361
432
  subscribe(callback) {
362
433
  const handler = () => callback(this.getState());
363
- // Listen to all events that might change state
364
434
  this.on('step-change', handler);
365
435
  this.on('tour-start', handler);
366
436
  this.on('tour-complete', handler);
367
437
  this.on('tour-skip', handler);
368
438
  this.on('error', handler);
369
439
  this.on('state-change', handler);
370
- // Return unsubscribe function
371
440
  return () => {
372
441
  this.off('step-change', handler);
373
442
  this.off('tour-start', handler);
@@ -379,6 +448,10 @@ class TourEngine {
379
448
  }
380
449
  }
381
450
 
451
+ /**
452
+ * Manages and executes tour actions with pluggable integration support.
453
+ * Handles clicks, navigation, and custom actions through registered integrations.
454
+ */
382
455
  class TourActions {
383
456
  constructor() {
384
457
  this.integrations = new Map();
@@ -389,11 +462,12 @@ class TourActions {
389
462
  unregisterIntegration(name) {
390
463
  this.integrations.delete(name);
391
464
  }
465
+ /**
466
+ * Executes a tour action using the appropriate integration or default handler.
467
+ */
392
468
  async execute(action, element) {
393
- // Find the appropriate integration for this action
394
469
  const integration = this.findIntegration(action);
395
470
  if (!integration) {
396
- // Fallback to default action handling
397
471
  await this.executeDefault(action, element);
398
472
  return;
399
473
  }
@@ -402,7 +476,6 @@ class TourActions {
402
476
  }
403
477
  catch (error) {
404
478
  console.error(`Integration ${integration.name} failed to execute action:`, error);
405
- // Fallback to default action handling
406
479
  await this.executeDefault(action, element);
407
480
  }
408
481
  }
@@ -414,6 +487,9 @@ class TourActions {
414
487
  }
415
488
  return null;
416
489
  }
490
+ /**
491
+ * Default action handler for built-in action types.
492
+ */
417
493
  async executeDefault(action, element) {
418
494
  const delay = action.delay || 0;
419
495
  if (delay > 0) {
@@ -427,7 +503,6 @@ class TourActions {
427
503
  await this.handleNavigate(action);
428
504
  break;
429
505
  case 'highlight':
430
- // Highlighting is handled by the UI components
431
506
  break;
432
507
  case 'custom':
433
508
  if (action.handler) {
@@ -446,11 +521,8 @@ class TourActions {
446
521
  if (!targetElement) {
447
522
  throw new Error(`Click target not found: ${action.target}`);
448
523
  }
449
- // Ensure element is visible and clickable
450
524
  targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
451
- // Wait a bit for scroll to complete
452
525
  await this.sleep(300);
453
- // Dispatch click event
454
526
  const clickEvent = new MouseEvent('click', {
455
527
  bubbles: true,
456
528
  cancelable: true,
@@ -462,17 +534,14 @@ class TourActions {
462
534
  if (!action.target) {
463
535
  throw new Error('Navigate action requires a target URL');
464
536
  }
465
- // Check if it's a hash navigation (same page)
466
537
  if (action.target.startsWith('#')) {
467
538
  window.location.hash = action.target;
468
539
  return;
469
540
  }
470
- // Check if it's a relative path
471
541
  if (action.target.startsWith('/')) {
472
542
  window.location.pathname = action.target;
473
543
  return;
474
544
  }
475
- // Full URL navigation
476
545
  window.location.href = action.target;
477
546
  }
478
547
  sleep(ms) {
@@ -822,7 +891,7 @@ function requireReactJsxRuntime_development () {
822
891
  object.$$typeof === REACT_ELEMENT_TYPE
823
892
  );
824
893
  }
825
- var React = require$$0,
894
+ var React$1 = React,
826
895
  REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
827
896
  REACT_PORTAL_TYPE = Symbol.for("react.portal"),
828
897
  REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
@@ -838,7 +907,7 @@ function requireReactJsxRuntime_development () {
838
907
  REACT_ACTIVITY_TYPE = Symbol.for("react.activity"),
839
908
  REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"),
840
909
  ReactSharedInternals =
841
- React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
910
+ React$1.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
842
911
  hasOwnProperty = Object.prototype.hasOwnProperty,
843
912
  isArrayImpl = Array.isArray,
844
913
  createTask = console.createTask
@@ -846,15 +915,15 @@ function requireReactJsxRuntime_development () {
846
915
  : function () {
847
916
  return null;
848
917
  };
849
- React = {
918
+ React$1 = {
850
919
  react_stack_bottom_frame: function (callStackForError) {
851
920
  return callStackForError();
852
921
  }
853
922
  };
854
923
  var specialPropKeyWarningShown;
855
924
  var didWarnAboutElementRef = {};
856
- var unknownOwnerDebugStack = React.react_stack_bottom_frame.bind(
857
- React,
925
+ var unknownOwnerDebugStack = React$1.react_stack_bottom_frame.bind(
926
+ React$1,
858
927
  UnknownOwner
859
928
  )();
860
929
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -900,7 +969,124 @@ if (process.env.NODE_ENV === 'production') {
900
969
 
901
970
  var jsxRuntimeExports = jsxRuntime.exports;
902
971
 
972
+ function useErrorHandler(options = {}) {
973
+ const { onError, logErrors = true, retryAttempts = 0, retryDelay = 1000, } = options;
974
+ const retryCountRef = useRef(new Map());
975
+ const handleError = useCallback((error, context) => {
976
+ if (logErrors) {
977
+ console.error(`Tour Error${context ? ` in ${context}` : ''}:`, error);
978
+ }
979
+ if (onError) {
980
+ try {
981
+ onError(error, context);
982
+ }
983
+ catch (handlerError) {
984
+ console.error('Error in custom error handler:', handlerError);
985
+ }
986
+ }
987
+ }, [onError, logErrors]);
988
+ const handleAsyncError = useCallback(async (asyncFn, context, fallback) => {
989
+ const contextKey = context || 'unknown';
990
+ const currentRetries = retryCountRef.current.get(contextKey) || 0;
991
+ try {
992
+ const result = await asyncFn();
993
+ retryCountRef.current.delete(contextKey);
994
+ return result;
995
+ }
996
+ catch (error) {
997
+ handleError(error, context);
998
+ if (currentRetries < retryAttempts) {
999
+ retryCountRef.current.set(contextKey, currentRetries + 1);
1000
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
1001
+ return handleAsyncError(asyncFn, context, fallback);
1002
+ }
1003
+ retryCountRef.current.delete(contextKey);
1004
+ return fallback;
1005
+ }
1006
+ }, [handleError, retryAttempts, retryDelay]);
1007
+ const wrapFunction = useCallback((fn, context) => {
1008
+ return ((...args) => {
1009
+ try {
1010
+ return fn(...args);
1011
+ }
1012
+ catch (error) {
1013
+ handleError(error, context);
1014
+ throw error;
1015
+ }
1016
+ });
1017
+ }, [handleError]);
1018
+ const wrapAsyncFunction = useCallback((fn, context) => {
1019
+ return (async (...args) => {
1020
+ try {
1021
+ return await fn(...args);
1022
+ }
1023
+ catch (error) {
1024
+ handleError(error, context);
1025
+ throw error; // Re-throw to maintain original behavior
1026
+ }
1027
+ });
1028
+ }, [handleError]);
1029
+ return {
1030
+ handleError,
1031
+ handleAsyncError,
1032
+ wrapFunction,
1033
+ wrapAsyncFunction,
1034
+ };
1035
+ }
1036
+ // Utility function for creating error-safe async operations
1037
+ function createSafeAsyncOperation(operation, options = {}) {
1038
+ const { context, fallback, onError, retryAttempts = 0, retryDelay = 1000, } = options;
1039
+ return async () => {
1040
+ let attempts = 0;
1041
+ while (attempts <= retryAttempts) {
1042
+ try {
1043
+ return await operation();
1044
+ }
1045
+ catch (error) {
1046
+ const errorObj = error;
1047
+ // Log error
1048
+ console.error(`Error in ${context || 'async operation'} (attempt ${attempts + 1}):`, errorObj);
1049
+ // Call custom error handler
1050
+ if (onError) {
1051
+ onError(errorObj);
1052
+ }
1053
+ attempts++;
1054
+ // If we've exhausted retries, return fallback
1055
+ if (attempts > retryAttempts) {
1056
+ return fallback;
1057
+ }
1058
+ // Wait before retrying
1059
+ if (retryDelay > 0) {
1060
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
1061
+ }
1062
+ }
1063
+ }
1064
+ return fallback;
1065
+ };
1066
+ }
1067
+ // Error boundary hook for functional components
1068
+ function useErrorBoundary() {
1069
+ const handleError = useCallback((error, _errorInfo) => {
1070
+ // This will be caught by the nearest ErrorBoundary
1071
+ throw error;
1072
+ }, []);
1073
+ return { handleError };
1074
+ }
1075
+
1076
+ /**
1077
+ * Hook that creates and manages a tour engine instance.
1078
+ * Provides tour control methods and state management with error handling.
1079
+ */
903
1080
  function useTourEngine(config) {
1081
+ // Use regular error handler to avoid ToastProvider dependency issues
1082
+ const errorHandler = useErrorHandler({
1083
+ onError: (error, context) => {
1084
+ console.error(`Tour Engine Error in ${context}:`, error);
1085
+ },
1086
+ retryAttempts: 1,
1087
+ retryDelay: 500,
1088
+ });
1089
+ const { handleAsyncError } = errorHandler;
904
1090
  const actions = useMemo(() => new TourActions(), []);
905
1091
  const engine = useMemo(() => {
906
1092
  const tourEngine = new TourEngine(config);
@@ -912,34 +1098,31 @@ function useTourEngine(config) {
912
1098
  return unsubscribe;
913
1099
  }, [engine]);
914
1100
  const start = useCallback(async () => {
915
- try {
916
- await engine.start();
917
- }
918
- catch (error) {
919
- console.error('Error in useTourEngine.start():', error);
920
- }
921
- }, [engine]);
1101
+ await handleAsyncError(() => engine.start(), 'tour start');
1102
+ }, [engine, handleAsyncError]);
922
1103
  const stop = useCallback(async () => {
923
1104
  await engine.stop();
924
1105
  }, [engine]);
925
1106
  const next = useCallback(async () => {
926
- const currentStep = engine.getCurrentStep();
927
- if (currentStep?.action) {
928
- // Execute the step action first
929
- await actions.execute(currentStep.action);
930
- }
931
- await engine.next();
932
- }, [engine, actions]);
1107
+ await handleAsyncError(async () => {
1108
+ const currentStep = engine.getCurrentStep();
1109
+ if (currentStep?.action) {
1110
+ // Execute the step action first
1111
+ await actions.execute(currentStep.action);
1112
+ }
1113
+ await engine.next();
1114
+ }, 'tour next');
1115
+ }, [engine, actions, handleAsyncError]);
933
1116
  const previous = useCallback(async () => {
934
- await engine.previous();
935
- }, [engine]);
1117
+ await handleAsyncError(() => engine.previous(), 'tour previous');
1118
+ }, [engine, handleAsyncError]);
936
1119
  const skip = useCallback(async () => {
937
- await engine.skip();
938
- }, [engine]);
1120
+ await handleAsyncError(() => engine.skip(), 'tour skip');
1121
+ }, [engine, handleAsyncError]);
939
1122
  const goToStep = useCallback(async (index) => {
940
- await engine.goToStep(index);
941
- }, [engine]);
942
- return {
1123
+ await handleAsyncError(() => engine.goToStep(index), `tour goToStep(${index})`);
1124
+ }, [engine, handleAsyncError]);
1125
+ const memoizedReturn = useMemo(() => ({
943
1126
  state,
944
1127
  start,
945
1128
  stop,
@@ -954,7 +1137,8 @@ function useTourEngine(config) {
954
1137
  currentStep: engine.getCurrentStep(),
955
1138
  engine,
956
1139
  actions,
957
- };
1140
+ }), [state, start, stop, next, previous, skip, goToStep, engine, actions]);
1141
+ return memoizedReturn;
958
1142
  }
959
1143
 
960
1144
  const TourContext = createContext(null);
@@ -983,16 +1167,24 @@ const defaultTheme = {
983
1167
  maxWidth: '384px',
984
1168
  },
985
1169
  };
986
- function TourProvider({ config, children }) {
1170
+ /**
1171
+ * Main provider component that initializes the tour system and provides context.
1172
+ * Wraps the application with tour functionality.
1173
+ */
1174
+ const TourProvider = React.memo(function TourProvider({ config, children }) {
987
1175
  const tourEngine = useTourEngine(config);
988
- const theme = { ...defaultTheme, ...config.theme };
989
- const contextValue = {
1176
+ const theme = useMemo(() => ({ ...defaultTheme, ...config.theme }), [config.theme]);
1177
+ const contextValue = useMemo(() => ({
990
1178
  ...tourEngine,
991
1179
  config,
992
1180
  theme,
993
- };
1181
+ }), [tourEngine, config, theme]);
994
1182
  return (jsxRuntimeExports.jsx(TourContext.Provider, { value: contextValue, children: children }));
995
- }
1183
+ });
1184
+ /**
1185
+ * Hook to access tour functionality and state.
1186
+ * Must be used within a TourProvider.
1187
+ */
996
1188
  function useTour() {
997
1189
  const context = useContext(TourContext);
998
1190
  if (!context) {
@@ -1001,6 +1193,9 @@ function useTour() {
1001
1193
  return context;
1002
1194
  }
1003
1195
 
1196
+ /**
1197
+ * Gets the absolute position of an element relative to the document.
1198
+ */
1004
1199
  function getElementPosition(element) {
1005
1200
  const rect = element.getBoundingClientRect();
1006
1201
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
@@ -1014,6 +1209,10 @@ function getElementPosition(element) {
1014
1209
  bottom: rect.bottom + scrollTop,
1015
1210
  };
1016
1211
  }
1212
+ /**
1213
+ * Calculates the optimal position for a popover relative to a target element.
1214
+ * Falls back to alternative placements if the preferred placement doesn't fit.
1215
+ */
1017
1216
  function calculatePopoverPosition(targetElement, popoverElement, preferredPlacement = 'top') {
1018
1217
  const targetPos = getElementPosition(targetElement);
1019
1218
  const popoverRect = popoverElement.getBoundingClientRect();
@@ -1023,7 +1222,7 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1023
1222
  scrollTop: window.pageYOffset || document.documentElement.scrollTop,
1024
1223
  scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
1025
1224
  };
1026
- const spacing = 12; // Gap between target and popover
1225
+ const spacing = 12;
1027
1226
  const positions = {
1028
1227
  top: {
1029
1228
  top: targetPos.top - popoverRect.height - spacing,
@@ -1051,12 +1250,12 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1051
1250
  placement: 'center',
1052
1251
  },
1053
1252
  };
1054
- // Check if preferred placement fits in viewport
1253
+ // Try preferred placement first
1055
1254
  const preferred = positions[preferredPlacement];
1056
1255
  if (isPositionInViewport(preferred, popoverRect, viewport)) {
1057
1256
  return preferred;
1058
1257
  }
1059
- // Try other placements in order of preference
1258
+ // Try fallback placements in order of preference
1060
1259
  const fallbackOrder = ['bottom', 'top', 'right', 'left', 'center'];
1061
1260
  for (const placement of fallbackOrder) {
1062
1261
  if (placement === preferredPlacement)
@@ -1066,16 +1265,18 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1066
1265
  return position;
1067
1266
  }
1068
1267
  }
1069
- // If nothing fits, use center as fallback
1070
1268
  return positions.center;
1071
1269
  }
1072
1270
  function isPositionInViewport(position, popoverRect, viewport) {
1073
- const margin = 16; // Minimum margin from viewport edges
1271
+ const margin = 16;
1074
1272
  return (position.left >= viewport.scrollLeft + margin &&
1075
1273
  position.left + popoverRect.width <= viewport.scrollLeft + viewport.width - margin &&
1076
1274
  position.top >= viewport.scrollTop + margin &&
1077
1275
  position.top + popoverRect.height <= viewport.scrollTop + viewport.height - margin);
1078
1276
  }
1277
+ /**
1278
+ * Smoothly scrolls an element into view.
1279
+ */
1079
1280
  function scrollToElement(element, behavior = 'smooth') {
1080
1281
  element.scrollIntoView({
1081
1282
  behavior,
@@ -1083,6 +1284,9 @@ function scrollToElement(element, behavior = 'smooth') {
1083
1284
  inline: 'center',
1084
1285
  });
1085
1286
  }
1287
+ /**
1288
+ * Checks if an element is currently visible in the viewport.
1289
+ */
1086
1290
  function isElementInViewport(element) {
1087
1291
  const rect = element.getBoundingClientRect();
1088
1292
  return (rect.top >= 0 &&
@@ -1097,6 +1301,10 @@ function getViewportCenter() {
1097
1301
  };
1098
1302
  }
1099
1303
 
1304
+ /**
1305
+ * Hook that manages element highlighting for tour steps.
1306
+ * Finds target elements, positions highlights, and handles dynamic content.
1307
+ */
1100
1308
  function useTourHighlight(step) {
1101
1309
  const [targetElement, setTargetElement] = useState(null);
1102
1310
  const [highlightStyle, setHighlightStyle] = useState({});
@@ -1108,6 +1316,7 @@ function useTourHighlight(step) {
1108
1316
  setIsVisible(false);
1109
1317
  return;
1110
1318
  }
1319
+ // Find target element and update highlight styling
1111
1320
  const findAndHighlightElement = () => {
1112
1321
  const element = step.highlight?.element ||
1113
1322
  document.querySelector(step.target);
@@ -1115,7 +1324,6 @@ function useTourHighlight(step) {
1115
1324
  setTargetElement(element);
1116
1325
  updateHighlightStyle(element, step.highlight);
1117
1326
  setIsVisible(true);
1118
- // Scroll to element if not in viewport
1119
1327
  if (!isElementInViewport(element)) {
1120
1328
  scrollToElement(element);
1121
1329
  }
@@ -1125,9 +1333,8 @@ function useTourHighlight(step) {
1125
1333
  setIsVisible(false);
1126
1334
  }
1127
1335
  };
1128
- // Initial attempt to find element
1129
1336
  findAndHighlightElement();
1130
- // Set up mutation observer to watch for DOM changes
1337
+ // Watch for DOM changes if element not found initially
1131
1338
  if (!targetElement && step.waitForElement !== false) {
1132
1339
  observerRef.current = new MutationObserver(() => {
1133
1340
  findAndHighlightElement();
@@ -1146,7 +1353,7 @@ function useTourHighlight(step) {
1146
1353
  }
1147
1354
  };
1148
1355
  }, [step?.target, step?.highlight, step?.waitForElement, targetElement]);
1149
- // Update highlight position when element moves (e.g., during animations)
1356
+ // Update highlight position when element moves
1150
1357
  useEffect(() => {
1151
1358
  if (!targetElement || !isVisible)
1152
1359
  return;
@@ -1157,7 +1364,6 @@ function useTourHighlight(step) {
1157
1364
  const handleResize = () => updatePosition();
1158
1365
  window.addEventListener('scroll', handleScroll, { passive: true });
1159
1366
  window.addEventListener('resize', handleResize, { passive: true });
1160
- // Use ResizeObserver to watch for element size changes
1161
1367
  let resizeObserver = null;
1162
1368
  if (window.ResizeObserver) {
1163
1369
  resizeObserver = new ResizeObserver(updatePosition);
@@ -1171,6 +1377,9 @@ function useTourHighlight(step) {
1171
1377
  }
1172
1378
  };
1173
1379
  }, [targetElement, isVisible, step?.highlight]);
1380
+ /**
1381
+ * Updates the highlight style based on element position and configuration.
1382
+ */
1174
1383
  const updateHighlightStyle = (element, config) => {
1175
1384
  const position = getElementPosition(element);
1176
1385
  const padding = config?.padding || 4;
@@ -1204,13 +1413,10 @@ function useTourHighlight(step) {
1204
1413
  };
1205
1414
  }
1206
1415
 
1207
- function TourOverlay({ className }) {
1416
+ const TourOverlay = React.memo(function TourOverlay({ className }) {
1208
1417
  const { state, theme, stop } = useTour();
1209
1418
  const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
1210
- if (!state.isRunning || !state.currentStep) {
1211
- return null;
1212
- }
1213
- const overlayStyle = {
1419
+ const overlayStyle = useMemo(() => ({
1214
1420
  position: 'fixed',
1215
1421
  top: 0,
1216
1422
  left: 0,
@@ -1220,12 +1426,15 @@ function TourOverlay({ className }) {
1220
1426
  opacity: theme.overlay?.opacity || 0.5,
1221
1427
  zIndex: (theme.zIndex || 9999) - 1,
1222
1428
  pointerEvents: 'auto',
1223
- };
1224
- const handleOverlayClick = () => {
1429
+ }), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
1430
+ const handleOverlayClick = useCallback(() => {
1225
1431
  if (state.currentStep?.canSkip !== false) {
1226
1432
  stop();
1227
1433
  }
1228
- };
1434
+ }, [state.currentStep?.canSkip, stop]);
1435
+ if (!state.isRunning || !state.currentStep) {
1436
+ return null;
1437
+ }
1229
1438
  return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: overlayStyle, onClick: handleOverlayClick, className: className, "data-tour-overlay": true }), isVisible && targetElement && (jsxRuntimeExports.jsx("div", { style: highlightStyle, "data-tour-highlight": true })), jsxRuntimeExports.jsx("style", { children: `
1230
1439
  @keyframes tour-highlight-pulse {
1231
1440
  0%, 100% {
@@ -1255,38 +1464,24 @@ function TourOverlay({ className }) {
1255
1464
  animation: tour-fade-in 0.3s ease-out;
1256
1465
  }
1257
1466
  ` })] }));
1258
- }
1467
+ });
1259
1468
 
1260
1469
  function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
1261
1470
 
1262
- function TourPopover({ className }) {
1471
+ const TourPopover = React.memo(function TourPopover({ className }) {
1263
1472
  const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
1264
1473
  const { targetElement } = useTourHighlight(state.currentStep);
1265
1474
  const popoverRef = useRef(null);
1266
1475
  const [position, setPosition] = useState({ top: 0, left: 0, placement: 'top' });
1267
- useEffect(() => {
1476
+ const updatePosition = useCallback(() => {
1268
1477
  if (!popoverRef.current || !targetElement || !state.currentStep)
1269
1478
  return;
1270
- const updatePosition = () => {
1271
- const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
1272
- setPosition(newPosition);
1273
- };
1274
- updatePosition();
1275
- const handleResize = () => updatePosition();
1276
- const handleScroll = () => updatePosition();
1277
- window.addEventListener('resize', handleResize);
1278
- window.addEventListener('scroll', handleScroll, { passive: true });
1279
- return () => {
1280
- window.removeEventListener('resize', handleResize);
1281
- window.removeEventListener('scroll', handleScroll);
1282
- };
1479
+ const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
1480
+ setPosition(newPosition);
1283
1481
  }, [targetElement, state.currentStep]);
1284
- if (!state.isRunning || !state.currentStep) {
1285
- return null;
1286
- }
1287
1482
  const step = state.currentStep;
1288
- const popoverConfig = step.popover || {};
1289
- const popoverStyle = {
1483
+ const popoverConfig = useMemo(() => step?.popover || {}, [step?.popover]);
1484
+ const popoverStyle = useMemo(() => ({
1290
1485
  position: 'absolute',
1291
1486
  top: position.top,
1292
1487
  left: position.left,
@@ -1311,23 +1506,37 @@ function TourPopover({ className }) {
1311
1506
  color: theme.textColor || '#1f2937',
1312
1507
  zIndex: theme.zIndex || 9999,
1313
1508
  animation: 'tour-fade-in 0.3s ease-out',
1314
- };
1315
- const handleNext = async () => {
1509
+ }), [position.top, position.left, theme]);
1510
+ const handleNext = useCallback(async () => {
1316
1511
  if (canGoNext || isLastStep) {
1317
1512
  await next();
1318
1513
  }
1319
- };
1320
- const handlePrevious = async () => {
1514
+ }, [canGoNext, isLastStep, next]);
1515
+ const handlePrevious = useCallback(async () => {
1321
1516
  if (canGoPrevious) {
1322
1517
  await previous();
1323
1518
  }
1324
- };
1325
- const handleSkip = async () => {
1519
+ }, [canGoPrevious, previous]);
1520
+ const handleSkip = useCallback(async () => {
1326
1521
  await skip();
1327
- };
1328
- const handleClose = async () => {
1522
+ }, [skip]);
1523
+ const handleClose = useCallback(async () => {
1329
1524
  await stop();
1330
- };
1525
+ }, [stop]);
1526
+ useEffect(() => {
1527
+ updatePosition();
1528
+ const handleResize = () => updatePosition();
1529
+ const handleScroll = () => updatePosition();
1530
+ window.addEventListener('resize', handleResize);
1531
+ window.addEventListener('scroll', handleScroll, { passive: true });
1532
+ return () => {
1533
+ window.removeEventListener('resize', handleResize);
1534
+ window.removeEventListener('scroll', handleScroll);
1535
+ };
1536
+ }, [updatePosition]);
1537
+ if (!state.isRunning || !state.currentStep) {
1538
+ return null;
1539
+ }
1331
1540
  return (jsxRuntimeExports.jsxs("div", { ref: popoverRef, style: {
1332
1541
  ...popoverStyle,
1333
1542
  pointerEvents: 'auto',
@@ -1362,22 +1571,22 @@ function TourPopover({ className }) {
1362
1571
  borderRadius: '12px',
1363
1572
  fontSize: '12px',
1364
1573
  fontWeight: '500',
1365
- }, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step.title) && (jsxRuntimeExports.jsx("h3", { style: {
1574
+ }, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step?.title) && (jsxRuntimeExports.jsx("h3", { style: {
1366
1575
  margin: '0 0 8px 0',
1367
1576
  fontSize: '16px',
1368
1577
  fontWeight: '600',
1369
1578
  color: theme.textColor || '#1f2937',
1370
- }, children: popoverConfig.title || step.title })), jsxRuntimeExports.jsx("div", { style: {
1579
+ }, children: popoverConfig.title || step?.title })), jsxRuntimeExports.jsx("div", { style: {
1371
1580
  margin: '0 0 16px 0',
1372
1581
  lineHeight: '1.5',
1373
1582
  color: theme.textColor || '#374151',
1374
- }, children: popoverConfig.content || step.content })] }), jsxRuntimeExports.jsxs("div", { style: {
1583
+ }, children: popoverConfig.content || step?.content })] }), jsxRuntimeExports.jsxs("div", { style: {
1375
1584
  display: 'flex',
1376
1585
  justifyContent: 'space-between',
1377
1586
  alignItems: 'center',
1378
1587
  padding: '0 20px 20px 20px',
1379
1588
  gap: '12px',
1380
- }, children: [(popoverConfig.showSkip !== false && step.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
1589
+ }, children: [(popoverConfig.showSkip !== false && step?.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
1381
1590
  handleSkip();
1382
1591
  }, style: {
1383
1592
  background: 'none',
@@ -1460,7 +1669,7 @@ function TourPopover({ className }) {
1460
1669
  transform: 'rotate(45deg)',
1461
1670
  ...getArrowPosition(position.placement),
1462
1671
  }, "data-tour-arrow": true }))] }));
1463
- }
1672
+ });
1464
1673
  function getArrowPosition(placement) {
1465
1674
  switch (placement) {
1466
1675
  case 'top':
@@ -1500,13 +1709,92 @@ function getArrowPosition(placement) {
1500
1709
  }
1501
1710
  }
1502
1711
 
1503
- function TourRunner({ className }) {
1712
+ class ErrorBoundary extends Component {
1713
+ constructor(props) {
1714
+ super(props);
1715
+ this.retryTimeoutId = null;
1716
+ this.retry = () => {
1717
+ this.setState({
1718
+ hasError: false,
1719
+ error: null,
1720
+ errorInfo: null,
1721
+ });
1722
+ };
1723
+ this.state = {
1724
+ hasError: false,
1725
+ error: null,
1726
+ errorInfo: null,
1727
+ };
1728
+ }
1729
+ static getDerivedStateFromError(error) {
1730
+ return {
1731
+ hasError: true,
1732
+ error,
1733
+ };
1734
+ }
1735
+ componentDidCatch(error, errorInfo) {
1736
+ this.setState({
1737
+ error,
1738
+ errorInfo,
1739
+ });
1740
+ if (this.props.onError) {
1741
+ this.props.onError(error, errorInfo);
1742
+ }
1743
+ console.error('Tour ErrorBoundary caught an error:', error, errorInfo);
1744
+ }
1745
+ componentWillUnmount() {
1746
+ if (this.retryTimeoutId) {
1747
+ clearTimeout(this.retryTimeoutId);
1748
+ }
1749
+ }
1750
+ render() {
1751
+ if (this.state.hasError && this.state.error && this.state.errorInfo) {
1752
+ if (this.props.fallback) {
1753
+ return this.props.fallback(this.state.error, this.state.errorInfo, this.retry);
1754
+ }
1755
+ return (jsxRuntimeExports.jsxs("div", { style: {
1756
+ padding: '20px',
1757
+ margin: '20px',
1758
+ border: '2px solid #ff6b6b',
1759
+ borderRadius: '8px',
1760
+ backgroundColor: '#fff5f5',
1761
+ color: '#c92a2a',
1762
+ fontFamily: 'system-ui, sans-serif'
1763
+ }, children: [jsxRuntimeExports.jsx("h2", { style: { margin: '0 0 10px 0', fontSize: '18px' }, children: "Something went wrong" }), jsxRuntimeExports.jsxs("details", { style: { marginBottom: '15px' }, children: [jsxRuntimeExports.jsx("summary", { style: { cursor: 'pointer', fontWeight: 'bold' }, children: "Error Details" }), jsxRuntimeExports.jsxs("pre", { style: {
1764
+ marginTop: '10px',
1765
+ padding: '10px',
1766
+ backgroundColor: '#f8f8f8',
1767
+ borderRadius: '4px',
1768
+ fontSize: '12px',
1769
+ overflow: 'auto'
1770
+ }, children: [this.state.error.message, this.state.error.stack && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: ['\n\n', this.state.error.stack] })), this.state.errorInfo.componentStack && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: ['\n\nComponent Stack:', this.state.errorInfo.componentStack] }))] })] }), jsxRuntimeExports.jsx("button", { onClick: this.retry, style: {
1771
+ padding: '8px 16px',
1772
+ backgroundColor: '#228be6',
1773
+ color: 'white',
1774
+ border: 'none',
1775
+ borderRadius: '4px',
1776
+ cursor: 'pointer',
1777
+ fontSize: '14px'
1778
+ }, children: "Try Again" })] }));
1779
+ }
1780
+ return this.props.children;
1781
+ }
1782
+ }
1783
+ function withErrorBoundary(Component, errorBoundaryProps) {
1784
+ const WrappedComponent = (props) => (jsxRuntimeExports.jsx(ErrorBoundary, { ...errorBoundaryProps, children: jsxRuntimeExports.jsx(Component, { ...props }) }));
1785
+ WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
1786
+ return WrappedComponent;
1787
+ }
1788
+
1789
+ const TourRunner = React.memo(function TourRunner({ className }) {
1504
1790
  const { state } = useTour();
1505
1791
  if (!state.isRunning) {
1506
1792
  return null;
1507
1793
  }
1508
- return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
1509
- }
1794
+ return (jsxRuntimeExports.jsxs(ErrorBoundary, { onError: (error, errorInfo) => {
1795
+ console.error('TourRunner error:', error, errorInfo);
1796
+ }, children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
1797
+ });
1510
1798
 
1511
1799
  class TabIntegration {
1512
1800
  constructor() {
@@ -1523,33 +1811,26 @@ class TabIntegration {
1523
1811
  if (!targetElement) {
1524
1812
  throw new Error(`Tab element not found: ${action.target}`);
1525
1813
  }
1526
- // Handle different tab implementations
1527
1814
  const tabValue = typeof action.value === 'string' ? action.value : undefined;
1528
1815
  await this.handleTabClick(targetElement, tabValue);
1529
1816
  }
1530
1817
  async handleTabClick(tabElement, tabValue) {
1531
- // Check for common tab patterns
1532
- // 1. Radix UI Tabs
1533
1818
  if (this.isRadixTab(tabElement)) {
1534
1819
  await this.handleRadixTab(tabElement);
1535
1820
  return;
1536
1821
  }
1537
- // 2. Material UI Tabs
1538
1822
  if (this.isMaterialUITab(tabElement)) {
1539
1823
  await this.handleMaterialUITab(tabElement);
1540
1824
  return;
1541
1825
  }
1542
- // 3. React Router tabs (links)
1543
1826
  if (this.isRouterTab(tabElement)) {
1544
1827
  await this.handleRouterTab(tabElement);
1545
1828
  return;
1546
1829
  }
1547
- // 4. Custom tabs with data attributes
1548
1830
  if (this.isCustomTab(tabElement)) {
1549
1831
  await this.handleCustomTab(tabElement, tabValue);
1550
1832
  return;
1551
1833
  }
1552
- // 5. Generic button/clickable tab
1553
1834
  await this.handleGenericTab(tabElement);
1554
1835
  }
1555
1836
  isRadixTab(element) {
@@ -2093,5 +2374,5 @@ class NavigationIntegration {
2093
2374
  }
2094
2375
  }
2095
2376
 
2096
- export { NavigationIntegration, TabIntegration, TourActions, TourEngine, TourOverlay, TourPopover, TourProvider, TourRunner, TourStorage, WizardIntegration, calculatePopoverPosition, getElementPosition, getViewportCenter, isElementInViewport, scrollToElement, useTour, useTourEngine, useTourHighlight };
2377
+ export { ErrorBoundary, NavigationIntegration, TabIntegration, TourActions, TourEngine, TourOverlay, TourPopover, TourProvider, TourRunner, TourStorage, WizardIntegration, calculatePopoverPosition, createSafeAsyncOperation, getElementPosition, getViewportCenter, isElementInViewport, scrollToElement, useErrorBoundary, useErrorHandler, useTour, useTourEngine, useTourHighlight, withErrorBoundary };
2097
2378
  //# sourceMappingURL=index.esm.js.map