@aladinbs/react-guided-tour 1.0.2 → 1.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 (39) hide show
  1. package/README.md +103 -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 +140 -6
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.esm.js +617 -144
  31. package/dist/index.esm.js.map +1 -1
  32. package/dist/index.js +641 -163
  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 +7 -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 -2
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var require$$0 = require('react');
3
+ var React = require('react');
4
4
 
5
5
  class TourStorage {
6
6
  constructor(key) {
@@ -97,6 +97,10 @@ class TourStorage {
97
97
  }
98
98
  }
99
99
 
100
+ /**
101
+ * Core tour engine that manages tour state, navigation, and lifecycle.
102
+ * Handles step progression, state persistence, and event emission.
103
+ */
100
104
  class TourEngine {
101
105
  constructor(config) {
102
106
  this.listeners = new Map();
@@ -127,6 +131,9 @@ class TourEngine {
127
131
  getConfig() {
128
132
  return { ...this.config };
129
133
  }
134
+ /**
135
+ * Starts the tour from the current step index.
136
+ */
130
137
  async start() {
131
138
  try {
132
139
  this.setState({
@@ -136,14 +143,22 @@ class TourEngine {
136
143
  });
137
144
  this.emit('tour-start', { tourId: this.config.id });
138
145
  await this.goToStep(this.state.currentStepIndex);
139
- // STOP HERE - don't auto-advance
140
146
  return;
141
147
  }
142
148
  catch (error) {
143
- console.error('Error in TourEngine.start():', error);
144
- this.handleError(error);
149
+ const errorObj = error;
150
+ console.error('Error in TourEngine.start():', errorObj);
151
+ this.handleError(errorObj);
152
+ this.setState({
153
+ isRunning: false,
154
+ isLoading: false
155
+ });
156
+ throw errorObj;
145
157
  }
146
158
  }
159
+ /**
160
+ * Advances to the next step in the tour.
161
+ */
147
162
  async next() {
148
163
  if (!this.state.isRunning) {
149
164
  return;
@@ -151,28 +166,34 @@ class TourEngine {
151
166
  try {
152
167
  const currentStep = this.getCurrentStep();
153
168
  if (!currentStep) {
154
- return;
169
+ throw new Error('No current step available for next operation');
155
170
  }
156
- // Execute after step hook
157
171
  if (currentStep.afterStep) {
158
- await currentStep.afterStep();
172
+ try {
173
+ await currentStep.afterStep();
174
+ }
175
+ catch (hookError) {
176
+ console.warn('Error in afterStep hook:', hookError);
177
+ }
159
178
  }
160
- // Mark step as completed
161
179
  this.markStepCompleted(currentStep.id);
162
- // Check if this is the last step
163
180
  if (this.state.currentStepIndex >= this.state.totalSteps - 1) {
164
181
  await this.complete();
165
182
  return;
166
183
  }
167
- // Go to next step
168
184
  const nextStepIndex = this.state.currentStepIndex + 1;
169
185
  await this.goToStep(nextStepIndex);
170
186
  }
171
187
  catch (error) {
172
- console.error('Error in TourEngine.next():', error);
173
- this.handleError(error);
188
+ const errorObj = error;
189
+ console.error('Error in TourEngine.next():', errorObj);
190
+ this.handleError(errorObj);
191
+ throw errorObj;
174
192
  }
175
193
  }
194
+ /**
195
+ * Goes back to the previous step in the tour.
196
+ */
176
197
  async previous() {
177
198
  if (!this.state.isRunning || this.state.currentStepIndex <= 0)
178
199
  return;
@@ -183,40 +204,65 @@ class TourEngine {
183
204
  this.handleError(error);
184
205
  }
185
206
  }
207
+ /**
208
+ * Navigates to a specific step by index.
209
+ */
186
210
  async goToStep(index) {
187
- if (index < 0 || index >= this.state.totalSteps)
188
- return;
211
+ if (index < 0 || index >= this.state.totalSteps) {
212
+ throw new Error(`Invalid step index: ${index}. Must be between 0 and ${this.state.totalSteps - 1}`);
213
+ }
189
214
  try {
190
215
  this.setState({ isLoading: true });
191
216
  const step = this.config.steps[index];
192
- // Execute before step hook
193
217
  if (step.beforeStep) {
194
- await step.beforeStep();
218
+ try {
219
+ await step.beforeStep();
220
+ }
221
+ catch (hookError) {
222
+ console.warn('Error in beforeStep hook:', hookError);
223
+ }
195
224
  }
196
- // Wait for element if specified
197
225
  if (step.waitForElement && step.target) {
198
- await this.waitForElement(step.target, step.waitTimeout || 5000);
226
+ try {
227
+ await this.waitForElement(step.target, step.waitTimeout || 5000);
228
+ }
229
+ catch (waitError) {
230
+ console.warn(`Element not found: ${step.target}. Continuing anyway.`);
231
+ }
199
232
  }
200
233
  this.setState({
201
234
  currentStepIndex: index,
202
235
  currentStep: step,
203
236
  isLoading: false,
204
237
  });
205
- // Save state if persistence is enabled
206
238
  if (this.config.storage?.remember) {
207
- this.storage.saveState(this.state);
239
+ try {
240
+ this.storage.saveState(this.state);
241
+ }
242
+ catch (storageError) {
243
+ console.warn('Failed to save tour state:', storageError);
244
+ }
208
245
  }
209
- // Emit step change event
210
246
  this.emit('step-change', { step, index });
211
247
  if (this.config.onStepChange) {
212
- this.config.onStepChange(step, index);
248
+ try {
249
+ this.config.onStepChange(step, index);
250
+ }
251
+ catch (callbackError) {
252
+ console.warn('Error in onStepChange callback:', callbackError);
253
+ }
213
254
  }
214
255
  }
215
256
  catch (error) {
216
257
  this.setState({ isLoading: false });
217
- this.handleError(error);
258
+ const errorObj = error;
259
+ this.handleError(errorObj);
260
+ throw errorObj;
218
261
  }
219
262
  }
263
+ /**
264
+ * Skips the current tour and marks it as skipped.
265
+ */
220
266
  async skip() {
221
267
  if (!this.state.isRunning)
222
268
  return;
@@ -230,7 +276,6 @@ class TourEngine {
230
276
  if (this.config.onSkip) {
231
277
  this.config.onSkip();
232
278
  }
233
- // Save skip state to localStorage
234
279
  if (this.config.storage?.remember) {
235
280
  this.storage.saveSkip();
236
281
  }
@@ -241,6 +286,9 @@ class TourEngine {
241
286
  this.handleError(error);
242
287
  }
243
288
  }
289
+ /**
290
+ * Completes the tour and marks it as finished.
291
+ */
244
292
  async complete() {
245
293
  try {
246
294
  this.setState({ isRunning: false, isCompleted: true });
@@ -248,7 +296,6 @@ class TourEngine {
248
296
  if (this.config.onComplete) {
249
297
  this.config.onComplete();
250
298
  }
251
- // Save completion state to localStorage
252
299
  if (this.config.storage?.remember) {
253
300
  this.storage.saveCompletion();
254
301
  }
@@ -279,11 +326,17 @@ class TourEngine {
279
326
  canGoPrevious() {
280
327
  return this.state.isRunning && !this.isFirstStep() && !this.state.isLoading;
281
328
  }
329
+ /**
330
+ * Determines if the tour should be shown based on completion/skip state.
331
+ */
282
332
  shouldShowTour() {
283
333
  if (!this.config.storage?.remember)
284
334
  return true;
285
335
  return !this.storage.isCompleted() && !this.storage.isSkipped();
286
336
  }
337
+ /**
338
+ * Resets tour state and clears localStorage.
339
+ */
287
340
  resetTourState() {
288
341
  if (this.config.storage?.remember) {
289
342
  this.storage.clearState();
@@ -330,18 +383,32 @@ class TourEngine {
330
383
  }
331
384
  }
332
385
  handleError(error) {
386
+ const errorMessage = error.message || 'Unknown error occurred';
387
+ const currentStep = this.getCurrentStep();
333
388
  this.setState({
334
- error: error.message,
389
+ error: errorMessage,
335
390
  isLoading: false,
336
391
  });
337
- this.emit('error', { error, step: this.getCurrentStep() || undefined });
392
+ this.emit('error', {
393
+ error,
394
+ step: currentStep || undefined,
395
+ tourId: this.config.id,
396
+ stepIndex: this.state.currentStepIndex,
397
+ timestamp: new Date().toISOString()
398
+ });
399
+ console.error('TourEngine Error Details:', {
400
+ message: errorMessage,
401
+ stack: error.stack,
402
+ tourId: this.config.id,
403
+ currentStep: currentStep?.id,
404
+ stepIndex: this.state.currentStepIndex,
405
+ state: this.state
406
+ });
338
407
  }
339
408
  setState(updates) {
340
409
  this.state = { ...this.state, ...updates };
341
- // Emit state change event to notify subscribers
342
410
  this.emit('state-change', this.state);
343
411
  }
344
- // Event system
345
412
  on(event, callback) {
346
413
  if (!this.listeners.has(event)) {
347
414
  this.listeners.set(event, new Set());
@@ -360,16 +427,18 @@ class TourEngine {
360
427
  callbacks.forEach(callback => callback(data));
361
428
  }
362
429
  }
430
+ /**
431
+ * Subscribes to tour state changes.
432
+ * Returns an unsubscribe function.
433
+ */
363
434
  subscribe(callback) {
364
435
  const handler = () => callback(this.getState());
365
- // Listen to all events that might change state
366
436
  this.on('step-change', handler);
367
437
  this.on('tour-start', handler);
368
438
  this.on('tour-complete', handler);
369
439
  this.on('tour-skip', handler);
370
440
  this.on('error', handler);
371
441
  this.on('state-change', handler);
372
- // Return unsubscribe function
373
442
  return () => {
374
443
  this.off('step-change', handler);
375
444
  this.off('tour-start', handler);
@@ -381,6 +450,10 @@ class TourEngine {
381
450
  }
382
451
  }
383
452
 
453
+ /**
454
+ * Manages and executes tour actions with pluggable integration support.
455
+ * Handles clicks, navigation, and custom actions through registered integrations.
456
+ */
384
457
  class TourActions {
385
458
  constructor() {
386
459
  this.integrations = new Map();
@@ -391,11 +464,12 @@ class TourActions {
391
464
  unregisterIntegration(name) {
392
465
  this.integrations.delete(name);
393
466
  }
467
+ /**
468
+ * Executes a tour action using the appropriate integration or default handler.
469
+ */
394
470
  async execute(action, element) {
395
- // Find the appropriate integration for this action
396
471
  const integration = this.findIntegration(action);
397
472
  if (!integration) {
398
- // Fallback to default action handling
399
473
  await this.executeDefault(action, element);
400
474
  return;
401
475
  }
@@ -404,7 +478,6 @@ class TourActions {
404
478
  }
405
479
  catch (error) {
406
480
  console.error(`Integration ${integration.name} failed to execute action:`, error);
407
- // Fallback to default action handling
408
481
  await this.executeDefault(action, element);
409
482
  }
410
483
  }
@@ -416,6 +489,9 @@ class TourActions {
416
489
  }
417
490
  return null;
418
491
  }
492
+ /**
493
+ * Default action handler for built-in action types.
494
+ */
419
495
  async executeDefault(action, element) {
420
496
  const delay = action.delay || 0;
421
497
  if (delay > 0) {
@@ -429,7 +505,6 @@ class TourActions {
429
505
  await this.handleNavigate(action);
430
506
  break;
431
507
  case 'highlight':
432
- // Highlighting is handled by the UI components
433
508
  break;
434
509
  case 'custom':
435
510
  if (action.handler) {
@@ -448,11 +523,8 @@ class TourActions {
448
523
  if (!targetElement) {
449
524
  throw new Error(`Click target not found: ${action.target}`);
450
525
  }
451
- // Ensure element is visible and clickable
452
526
  targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
453
- // Wait a bit for scroll to complete
454
527
  await this.sleep(300);
455
- // Dispatch click event
456
528
  const clickEvent = new MouseEvent('click', {
457
529
  bubbles: true,
458
530
  cancelable: true,
@@ -464,17 +536,14 @@ class TourActions {
464
536
  if (!action.target) {
465
537
  throw new Error('Navigate action requires a target URL');
466
538
  }
467
- // Check if it's a hash navigation (same page)
468
539
  if (action.target.startsWith('#')) {
469
540
  window.location.hash = action.target;
470
541
  return;
471
542
  }
472
- // Check if it's a relative path
473
543
  if (action.target.startsWith('/')) {
474
544
  window.location.pathname = action.target;
475
545
  return;
476
546
  }
477
- // Full URL navigation
478
547
  window.location.href = action.target;
479
548
  }
480
549
  sleep(ms) {
@@ -824,7 +893,7 @@ function requireReactJsxRuntime_development () {
824
893
  object.$$typeof === REACT_ELEMENT_TYPE
825
894
  );
826
895
  }
827
- var React = require$$0,
896
+ var React$1 = React,
828
897
  REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
829
898
  REACT_PORTAL_TYPE = Symbol.for("react.portal"),
830
899
  REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
@@ -840,7 +909,7 @@ function requireReactJsxRuntime_development () {
840
909
  REACT_ACTIVITY_TYPE = Symbol.for("react.activity"),
841
910
  REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"),
842
911
  ReactSharedInternals =
843
- React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
912
+ React$1.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
844
913
  hasOwnProperty = Object.prototype.hasOwnProperty,
845
914
  isArrayImpl = Array.isArray,
846
915
  createTask = console.createTask
@@ -848,15 +917,15 @@ function requireReactJsxRuntime_development () {
848
917
  : function () {
849
918
  return null;
850
919
  };
851
- React = {
920
+ React$1 = {
852
921
  react_stack_bottom_frame: function (callStackForError) {
853
922
  return callStackForError();
854
923
  }
855
924
  };
856
925
  var specialPropKeyWarningShown;
857
926
  var didWarnAboutElementRef = {};
858
- var unknownOwnerDebugStack = React.react_stack_bottom_frame.bind(
859
- React,
927
+ var unknownOwnerDebugStack = React$1.react_stack_bottom_frame.bind(
928
+ React$1,
860
929
  UnknownOwner
861
930
  )();
862
931
  var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
@@ -902,46 +971,160 @@ if (process.env.NODE_ENV === 'production') {
902
971
 
903
972
  var jsxRuntimeExports = jsxRuntime.exports;
904
973
 
974
+ function useErrorHandler(options = {}) {
975
+ const { onError, logErrors = true, retryAttempts = 0, retryDelay = 1000, } = options;
976
+ const retryCountRef = React.useRef(new Map());
977
+ const handleError = React.useCallback((error, context) => {
978
+ if (logErrors) {
979
+ console.error(`Tour Error${context ? ` in ${context}` : ''}:`, error);
980
+ }
981
+ if (onError) {
982
+ try {
983
+ onError(error, context);
984
+ }
985
+ catch (handlerError) {
986
+ console.error('Error in custom error handler:', handlerError);
987
+ }
988
+ }
989
+ }, [onError, logErrors]);
990
+ const handleAsyncError = React.useCallback(async (asyncFn, context, fallback) => {
991
+ const contextKey = context || 'unknown';
992
+ const currentRetries = retryCountRef.current.get(contextKey) || 0;
993
+ try {
994
+ const result = await asyncFn();
995
+ retryCountRef.current.delete(contextKey);
996
+ return result;
997
+ }
998
+ catch (error) {
999
+ handleError(error, context);
1000
+ if (currentRetries < retryAttempts) {
1001
+ retryCountRef.current.set(contextKey, currentRetries + 1);
1002
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
1003
+ return handleAsyncError(asyncFn, context, fallback);
1004
+ }
1005
+ retryCountRef.current.delete(contextKey);
1006
+ return fallback;
1007
+ }
1008
+ }, [handleError, retryAttempts, retryDelay]);
1009
+ const wrapFunction = React.useCallback((fn, context) => {
1010
+ return ((...args) => {
1011
+ try {
1012
+ return fn(...args);
1013
+ }
1014
+ catch (error) {
1015
+ handleError(error, context);
1016
+ throw error;
1017
+ }
1018
+ });
1019
+ }, [handleError]);
1020
+ const wrapAsyncFunction = React.useCallback((fn, context) => {
1021
+ return (async (...args) => {
1022
+ try {
1023
+ return await fn(...args);
1024
+ }
1025
+ catch (error) {
1026
+ handleError(error, context);
1027
+ throw error; // Re-throw to maintain original behavior
1028
+ }
1029
+ });
1030
+ }, [handleError]);
1031
+ return {
1032
+ handleError,
1033
+ handleAsyncError,
1034
+ wrapFunction,
1035
+ wrapAsyncFunction,
1036
+ };
1037
+ }
1038
+ // Utility function for creating error-safe async operations
1039
+ function createSafeAsyncOperation(operation, options = {}) {
1040
+ const { context, fallback, onError, retryAttempts = 0, retryDelay = 1000, } = options;
1041
+ return async () => {
1042
+ let attempts = 0;
1043
+ while (attempts <= retryAttempts) {
1044
+ try {
1045
+ return await operation();
1046
+ }
1047
+ catch (error) {
1048
+ const errorObj = error;
1049
+ // Log error
1050
+ console.error(`Error in ${context || 'async operation'} (attempt ${attempts + 1}):`, errorObj);
1051
+ // Call custom error handler
1052
+ if (onError) {
1053
+ onError(errorObj);
1054
+ }
1055
+ attempts++;
1056
+ // If we've exhausted retries, return fallback
1057
+ if (attempts > retryAttempts) {
1058
+ return fallback;
1059
+ }
1060
+ // Wait before retrying
1061
+ if (retryDelay > 0) {
1062
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
1063
+ }
1064
+ }
1065
+ }
1066
+ return fallback;
1067
+ };
1068
+ }
1069
+ // Error boundary hook for functional components
1070
+ function useErrorBoundary() {
1071
+ const handleError = React.useCallback((error, _errorInfo) => {
1072
+ // This will be caught by the nearest ErrorBoundary
1073
+ throw error;
1074
+ }, []);
1075
+ return { handleError };
1076
+ }
1077
+
1078
+ /**
1079
+ * Hook that creates and manages a tour engine instance.
1080
+ * Provides tour control methods and state management with error handling.
1081
+ */
905
1082
  function useTourEngine(config) {
906
- const actions = require$$0.useMemo(() => new TourActions(), []);
907
- const engine = require$$0.useMemo(() => {
1083
+ // Use regular error handler to avoid ToastProvider dependency issues
1084
+ const errorHandler = useErrorHandler({
1085
+ onError: (error, context) => {
1086
+ console.error(`Tour Engine Error in ${context}:`, error);
1087
+ },
1088
+ retryAttempts: 1,
1089
+ retryDelay: 500,
1090
+ });
1091
+ const { handleAsyncError } = errorHandler;
1092
+ const actions = React.useMemo(() => new TourActions(), []);
1093
+ const engine = React.useMemo(() => {
908
1094
  const tourEngine = new TourEngine(config);
909
1095
  return tourEngine;
910
1096
  }, [config]);
911
- const [state, setState] = require$$0.useState(engine.getState());
912
- require$$0.useEffect(() => {
1097
+ const [state, setState] = React.useState(engine.getState());
1098
+ React.useEffect(() => {
913
1099
  const unsubscribe = engine.subscribe(setState);
914
1100
  return unsubscribe;
915
1101
  }, [engine]);
916
- const start = require$$0.useCallback(async () => {
917
- try {
918
- await engine.start();
919
- }
920
- catch (error) {
921
- console.error('Error in useTourEngine.start():', error);
922
- }
923
- }, [engine]);
924
- const stop = require$$0.useCallback(async () => {
1102
+ const start = React.useCallback(async () => {
1103
+ await handleAsyncError(() => engine.start(), 'tour start');
1104
+ }, [engine, handleAsyncError]);
1105
+ const stop = React.useCallback(async () => {
925
1106
  await engine.stop();
926
1107
  }, [engine]);
927
- const next = require$$0.useCallback(async () => {
928
- const currentStep = engine.getCurrentStep();
929
- if (currentStep?.action) {
930
- // Execute the step action first
931
- await actions.execute(currentStep.action);
932
- }
933
- await engine.next();
934
- }, [engine, actions]);
935
- const previous = require$$0.useCallback(async () => {
936
- await engine.previous();
937
- }, [engine]);
938
- const skip = require$$0.useCallback(async () => {
939
- await engine.skip();
940
- }, [engine]);
941
- const goToStep = require$$0.useCallback(async (index) => {
942
- await engine.goToStep(index);
943
- }, [engine]);
944
- return {
1108
+ const next = React.useCallback(async () => {
1109
+ await handleAsyncError(async () => {
1110
+ const currentStep = engine.getCurrentStep();
1111
+ if (currentStep?.action) {
1112
+ // Execute the step action first
1113
+ await actions.execute(currentStep.action);
1114
+ }
1115
+ await engine.next();
1116
+ }, 'tour next');
1117
+ }, [engine, actions, handleAsyncError]);
1118
+ const previous = React.useCallback(async () => {
1119
+ await handleAsyncError(() => engine.previous(), 'tour previous');
1120
+ }, [engine, handleAsyncError]);
1121
+ const skip = React.useCallback(async () => {
1122
+ await handleAsyncError(() => engine.skip(), 'tour skip');
1123
+ }, [engine, handleAsyncError]);
1124
+ const goToStep = React.useCallback(async (index) => {
1125
+ await handleAsyncError(() => engine.goToStep(index), `tour goToStep(${index})`);
1126
+ }, [engine, handleAsyncError]);
1127
+ const memoizedReturn = React.useMemo(() => ({
945
1128
  state,
946
1129
  start,
947
1130
  stop,
@@ -956,10 +1139,11 @@ function useTourEngine(config) {
956
1139
  currentStep: engine.getCurrentStep(),
957
1140
  engine,
958
1141
  actions,
959
- };
1142
+ }), [state, start, stop, next, previous, skip, goToStep, engine, actions]);
1143
+ return memoizedReturn;
960
1144
  }
961
1145
 
962
- const TourContext = require$$0.createContext(null);
1146
+ const TourContext = React.createContext(null);
963
1147
  const defaultTheme = {
964
1148
  primaryColor: '#3b82f6',
965
1149
  backgroundColor: '#ffffff',
@@ -985,24 +1169,35 @@ const defaultTheme = {
985
1169
  maxWidth: '384px',
986
1170
  },
987
1171
  };
988
- function TourProvider({ config, children }) {
1172
+ /**
1173
+ * Main provider component that initializes the tour system and provides context.
1174
+ * Wraps the application with tour functionality.
1175
+ */
1176
+ const TourProvider = React.memo(function TourProvider({ config, children }) {
989
1177
  const tourEngine = useTourEngine(config);
990
- const theme = { ...defaultTheme, ...config.theme };
991
- const contextValue = {
1178
+ const theme = React.useMemo(() => ({ ...defaultTheme, ...config.theme }), [config.theme]);
1179
+ const contextValue = React.useMemo(() => ({
992
1180
  ...tourEngine,
993
1181
  config,
994
1182
  theme,
995
- };
1183
+ }), [tourEngine, config, theme]);
996
1184
  return (jsxRuntimeExports.jsx(TourContext.Provider, { value: contextValue, children: children }));
997
- }
1185
+ });
1186
+ /**
1187
+ * Hook to access tour functionality and state.
1188
+ * Must be used within a TourProvider.
1189
+ */
998
1190
  function useTour() {
999
- const context = require$$0.useContext(TourContext);
1191
+ const context = React.useContext(TourContext);
1000
1192
  if (!context) {
1001
1193
  throw new Error('useTour must be used within a TourProvider');
1002
1194
  }
1003
1195
  return context;
1004
1196
  }
1005
1197
 
1198
+ /**
1199
+ * Gets the absolute position of an element relative to the document.
1200
+ */
1006
1201
  function getElementPosition(element) {
1007
1202
  const rect = element.getBoundingClientRect();
1008
1203
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
@@ -1016,6 +1211,10 @@ function getElementPosition(element) {
1016
1211
  bottom: rect.bottom + scrollTop,
1017
1212
  };
1018
1213
  }
1214
+ /**
1215
+ * Calculates the optimal position for a popover relative to a target element.
1216
+ * Falls back to alternative placements if the preferred placement doesn't fit.
1217
+ */
1019
1218
  function calculatePopoverPosition(targetElement, popoverElement, preferredPlacement = 'top') {
1020
1219
  const targetPos = getElementPosition(targetElement);
1021
1220
  const popoverRect = popoverElement.getBoundingClientRect();
@@ -1025,7 +1224,7 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1025
1224
  scrollTop: window.pageYOffset || document.documentElement.scrollTop,
1026
1225
  scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
1027
1226
  };
1028
- const spacing = 12; // Gap between target and popover
1227
+ const spacing = 12;
1029
1228
  const positions = {
1030
1229
  top: {
1031
1230
  top: targetPos.top - popoverRect.height - spacing,
@@ -1053,12 +1252,12 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1053
1252
  placement: 'center',
1054
1253
  },
1055
1254
  };
1056
- // Check if preferred placement fits in viewport
1255
+ // Try preferred placement first
1057
1256
  const preferred = positions[preferredPlacement];
1058
1257
  if (isPositionInViewport(preferred, popoverRect, viewport)) {
1059
1258
  return preferred;
1060
1259
  }
1061
- // Try other placements in order of preference
1260
+ // Try fallback placements in order of preference
1062
1261
  const fallbackOrder = ['bottom', 'top', 'right', 'left', 'center'];
1063
1262
  for (const placement of fallbackOrder) {
1064
1263
  if (placement === preferredPlacement)
@@ -1068,16 +1267,18 @@ function calculatePopoverPosition(targetElement, popoverElement, preferredPlacem
1068
1267
  return position;
1069
1268
  }
1070
1269
  }
1071
- // If nothing fits, use center as fallback
1072
1270
  return positions.center;
1073
1271
  }
1074
1272
  function isPositionInViewport(position, popoverRect, viewport) {
1075
- const margin = 16; // Minimum margin from viewport edges
1273
+ const margin = 16;
1076
1274
  return (position.left >= viewport.scrollLeft + margin &&
1077
1275
  position.left + popoverRect.width <= viewport.scrollLeft + viewport.width - margin &&
1078
1276
  position.top >= viewport.scrollTop + margin &&
1079
1277
  position.top + popoverRect.height <= viewport.scrollTop + viewport.height - margin);
1080
1278
  }
1279
+ /**
1280
+ * Smoothly scrolls an element into view.
1281
+ */
1081
1282
  function scrollToElement(element, behavior = 'smooth') {
1082
1283
  element.scrollIntoView({
1083
1284
  behavior,
@@ -1085,6 +1286,9 @@ function scrollToElement(element, behavior = 'smooth') {
1085
1286
  inline: 'center',
1086
1287
  });
1087
1288
  }
1289
+ /**
1290
+ * Checks if an element is currently visible in the viewport.
1291
+ */
1088
1292
  function isElementInViewport(element) {
1089
1293
  const rect = element.getBoundingClientRect();
1090
1294
  return (rect.top >= 0 &&
@@ -1099,17 +1303,22 @@ function getViewportCenter() {
1099
1303
  };
1100
1304
  }
1101
1305
 
1306
+ /**
1307
+ * Hook that manages element highlighting for tour steps.
1308
+ * Finds target elements, positions highlights, and handles dynamic content.
1309
+ */
1102
1310
  function useTourHighlight(step) {
1103
- const [targetElement, setTargetElement] = require$$0.useState(null);
1104
- const [highlightStyle, setHighlightStyle] = require$$0.useState({});
1105
- const [isVisible, setIsVisible] = require$$0.useState(false);
1106
- const observerRef = require$$0.useRef(null);
1107
- require$$0.useEffect(() => {
1311
+ const [targetElement, setTargetElement] = React.useState(null);
1312
+ const [highlightStyle, setHighlightStyle] = React.useState({});
1313
+ const [isVisible, setIsVisible] = React.useState(false);
1314
+ const observerRef = React.useRef(null);
1315
+ React.useEffect(() => {
1108
1316
  if (!step?.target) {
1109
1317
  setTargetElement(null);
1110
1318
  setIsVisible(false);
1111
1319
  return;
1112
1320
  }
1321
+ // Find target element and update highlight styling
1113
1322
  const findAndHighlightElement = () => {
1114
1323
  const element = step.highlight?.element ||
1115
1324
  document.querySelector(step.target);
@@ -1117,7 +1326,6 @@ function useTourHighlight(step) {
1117
1326
  setTargetElement(element);
1118
1327
  updateHighlightStyle(element, step.highlight);
1119
1328
  setIsVisible(true);
1120
- // Scroll to element if not in viewport
1121
1329
  if (!isElementInViewport(element)) {
1122
1330
  scrollToElement(element);
1123
1331
  }
@@ -1127,9 +1335,8 @@ function useTourHighlight(step) {
1127
1335
  setIsVisible(false);
1128
1336
  }
1129
1337
  };
1130
- // Initial attempt to find element
1131
1338
  findAndHighlightElement();
1132
- // Set up mutation observer to watch for DOM changes
1339
+ // Watch for DOM changes if element not found initially
1133
1340
  if (!targetElement && step.waitForElement !== false) {
1134
1341
  observerRef.current = new MutationObserver(() => {
1135
1342
  findAndHighlightElement();
@@ -1148,8 +1355,8 @@ function useTourHighlight(step) {
1148
1355
  }
1149
1356
  };
1150
1357
  }, [step?.target, step?.highlight, step?.waitForElement, targetElement]);
1151
- // Update highlight position when element moves (e.g., during animations)
1152
- require$$0.useEffect(() => {
1358
+ // Update highlight position when element moves
1359
+ React.useEffect(() => {
1153
1360
  if (!targetElement || !isVisible)
1154
1361
  return;
1155
1362
  const updatePosition = () => {
@@ -1159,7 +1366,6 @@ function useTourHighlight(step) {
1159
1366
  const handleResize = () => updatePosition();
1160
1367
  window.addEventListener('scroll', handleScroll, { passive: true });
1161
1368
  window.addEventListener('resize', handleResize, { passive: true });
1162
- // Use ResizeObserver to watch for element size changes
1163
1369
  let resizeObserver = null;
1164
1370
  if (window.ResizeObserver) {
1165
1371
  resizeObserver = new ResizeObserver(updatePosition);
@@ -1173,6 +1379,9 @@ function useTourHighlight(step) {
1173
1379
  }
1174
1380
  };
1175
1381
  }, [targetElement, isVisible, step?.highlight]);
1382
+ /**
1383
+ * Updates the highlight style based on element position and configuration.
1384
+ */
1176
1385
  const updateHighlightStyle = (element, config) => {
1177
1386
  const position = getElementPosition(element);
1178
1387
  const padding = config?.padding || 4;
@@ -1206,13 +1415,23 @@ function useTourHighlight(step) {
1206
1415
  };
1207
1416
  }
1208
1417
 
1209
- function TourOverlay({ className }) {
1210
- const { state, theme, stop } = useTour();
1418
+ const TourOverlay = React.memo(function TourOverlay({ className }) {
1419
+ const { state, theme, stop, config, next } = useTour();
1211
1420
  const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
1212
- if (!state.isRunning || !state.currentStep) {
1213
- return null;
1214
- }
1215
- const overlayStyle = {
1421
+ const [scrollTrigger, setScrollTrigger] = React.useState(0);
1422
+ // Determine if interactions should be blocked
1423
+ const shouldBlockInteractions = React.useMemo(() => {
1424
+ const stepBlocking = state.currentStep?.blockInteractions;
1425
+ const globalBlocking = config.blockInteractions;
1426
+ return stepBlocking !== undefined ? stepBlocking : globalBlocking;
1427
+ }, [state.currentStep?.blockInteractions, config.blockInteractions]);
1428
+ // Determine if click-to-advance is enabled
1429
+ const shouldClickToAdvance = React.useMemo(() => {
1430
+ const stepClickToAdvance = state.currentStep?.clickToAdvance;
1431
+ const globalClickToAdvance = config.clickToAdvance;
1432
+ return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
1433
+ }, [state.currentStep?.clickToAdvance, config.clickToAdvance]);
1434
+ const overlayStyle = React.useMemo(() => ({
1216
1435
  position: 'fixed',
1217
1436
  top: 0,
1218
1437
  left: 0,
@@ -1222,13 +1441,151 @@ function TourOverlay({ className }) {
1222
1441
  opacity: theme.overlay?.opacity || 0.5,
1223
1442
  zIndex: (theme.zIndex || 9999) - 1,
1224
1443
  pointerEvents: 'auto',
1225
- };
1226
- const handleOverlayClick = () => {
1227
- if (state.currentStep?.canSkip !== false) {
1444
+ }), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
1445
+ // Create cutout style for the highlighted element
1446
+ const cutoutOverlayStyle = React.useMemo(() => {
1447
+ if ((!shouldBlockInteractions && !shouldClickToAdvance) || !targetElement || !isVisible) {
1448
+ return overlayStyle;
1449
+ }
1450
+ // Use getBoundingClientRect for viewport-relative coordinates (perfect for fixed overlay)
1451
+ const rect = targetElement.getBoundingClientRect();
1452
+ const padding = state.currentStep?.highlight?.padding || 8;
1453
+ // Ensure coordinates are within viewport bounds
1454
+ const left = Math.max(0, rect.left - padding);
1455
+ const top = Math.max(0, rect.top - padding);
1456
+ const right = Math.min(window.innerWidth, rect.right + padding);
1457
+ const bottom = Math.min(window.innerHeight, rect.bottom + padding);
1458
+ return {
1459
+ ...overlayStyle,
1460
+ clipPath: `polygon(
1461
+ 0% 0%,
1462
+ 0% 100%,
1463
+ ${left}px 100%,
1464
+ ${left}px ${top}px,
1465
+ ${right}px ${top}px,
1466
+ ${right}px ${bottom}px,
1467
+ ${left}px ${bottom}px,
1468
+ ${left}px 100%,
1469
+ 100% 100%,
1470
+ 100% 0%
1471
+ )`,
1472
+ };
1473
+ }, [overlayStyle, shouldBlockInteractions, shouldClickToAdvance, targetElement, isVisible, state.currentStep?.highlight?.padding, scrollTrigger]); // eslint-disable-line react-hooks/exhaustive-deps
1474
+ // Force re-render of cutout overlay on scroll to maintain proper positioning
1475
+ React.useEffect(() => {
1476
+ if (!shouldBlockInteractions && !shouldClickToAdvance) {
1477
+ return;
1478
+ }
1479
+ const handleScroll = () => {
1480
+ // Force recalculation by updating scroll trigger
1481
+ setScrollTrigger(prev => prev + 1);
1482
+ };
1483
+ window.addEventListener('scroll', handleScroll, { passive: true });
1484
+ window.addEventListener('resize', handleScroll, { passive: true });
1485
+ return () => {
1486
+ window.removeEventListener('scroll', handleScroll);
1487
+ window.removeEventListener('resize', handleScroll);
1488
+ };
1489
+ }, [shouldBlockInteractions, shouldClickToAdvance]);
1490
+ const handleOverlayClick = React.useCallback((e) => {
1491
+ // If interactions are blocked, prevent the click from propagating
1492
+ if (shouldBlockInteractions) {
1493
+ e.preventDefault();
1494
+ e.stopPropagation();
1495
+ return;
1496
+ }
1497
+ // Allow clicking outside to close tour if not blocking interactions
1498
+ if (config.allowClickOutside !== false && state.currentStep?.canSkip !== false) {
1228
1499
  stop();
1229
1500
  }
1230
- };
1231
- 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: `
1501
+ }, [shouldBlockInteractions, config.allowClickOutside, state.currentStep?.canSkip, stop]);
1502
+ // Handle click-to-advance functionality
1503
+ React.useEffect(() => {
1504
+ if (!shouldClickToAdvance || !targetElement || !state.isRunning) {
1505
+ return;
1506
+ }
1507
+ // Ensure target element is above the overlay when click-to-advance is enabled
1508
+ const originalZIndex = targetElement.style.zIndex;
1509
+ const originalPosition = targetElement.style.position;
1510
+ targetElement.style.position = targetElement.style.position || 'relative';
1511
+ targetElement.style.zIndex = String((theme.zIndex || 9999) + 1);
1512
+ const handleTargetClick = (_e) => {
1513
+ // Don't prevent default or stop propagation - let the element's normal behavior work
1514
+ // Just advance the tour after a small delay to allow the click to be processed
1515
+ setTimeout(() => {
1516
+ next();
1517
+ }, 100);
1518
+ };
1519
+ targetElement.addEventListener('click', handleTargetClick, true);
1520
+ return () => {
1521
+ targetElement.removeEventListener('click', handleTargetClick, true);
1522
+ // Restore original styles
1523
+ targetElement.style.zIndex = originalZIndex;
1524
+ targetElement.style.position = originalPosition;
1525
+ };
1526
+ }, [shouldClickToAdvance, targetElement, state.isRunning, next, theme.zIndex]);
1527
+ // Block interactions on the entire page when blocking is enabled
1528
+ React.useEffect(() => {
1529
+ if (!shouldBlockInteractions || !state.isRunning) {
1530
+ return;
1531
+ }
1532
+ const handleGlobalClick = (e) => {
1533
+ const target = e.target;
1534
+ // Allow clicks on the tour target element and its children
1535
+ if (targetElement && (targetElement.contains(target) || targetElement === target)) {
1536
+ // If click-to-advance is enabled, let the target click handler deal with it
1537
+ if (shouldClickToAdvance) {
1538
+ return;
1539
+ }
1540
+ return;
1541
+ }
1542
+ // Allow clicks on tour UI elements (popover, buttons, etc.)
1543
+ if (target.closest('[data-tour-popover]') ||
1544
+ target.closest('[data-tour-highlight]') ||
1545
+ target.closest('[data-tour-overlay]')) {
1546
+ return;
1547
+ }
1548
+ // Block all other clicks
1549
+ e.preventDefault();
1550
+ e.stopPropagation();
1551
+ };
1552
+ const handleGlobalKeydown = (e) => {
1553
+ // Allow tour navigation keys
1554
+ if (['Escape', 'ArrowLeft', 'ArrowRight', 'Enter', 'Space'].includes(e.key)) {
1555
+ return;
1556
+ }
1557
+ // Block other keyboard interactions
1558
+ e.preventDefault();
1559
+ e.stopPropagation();
1560
+ };
1561
+ // Add event listeners to capture phase to block interactions early
1562
+ document.addEventListener('click', handleGlobalClick, true);
1563
+ document.addEventListener('keydown', handleGlobalKeydown, true);
1564
+ document.addEventListener('mousedown', handleGlobalClick, true);
1565
+ document.addEventListener('touchstart', handleGlobalClick, true);
1566
+ return () => {
1567
+ document.removeEventListener('click', handleGlobalClick, true);
1568
+ document.removeEventListener('keydown', handleGlobalKeydown, true);
1569
+ document.removeEventListener('mousedown', handleGlobalClick, true);
1570
+ document.removeEventListener('touchstart', handleGlobalClick, true);
1571
+ };
1572
+ }, [shouldBlockInteractions, state.isRunning, targetElement, shouldClickToAdvance]);
1573
+ if (!state.isRunning || !state.currentStep) {
1574
+ return null;
1575
+ }
1576
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: (shouldBlockInteractions || shouldClickToAdvance) ? cutoutOverlayStyle : overlayStyle, onClick: handleOverlayClick, className: className, "data-tour-overlay": true }), isVisible && targetElement && (jsxRuntimeExports.jsx("div", { style: highlightStyle, "data-tour-highlight": true })), shouldBlockInteractions && (jsxRuntimeExports.jsx("div", { style: {
1577
+ position: 'fixed',
1578
+ top: '20px',
1579
+ right: '20px',
1580
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
1581
+ color: 'white',
1582
+ padding: '8px 12px',
1583
+ borderRadius: '4px',
1584
+ fontSize: '12px',
1585
+ zIndex: (theme.zIndex || 9999) + 1,
1586
+ pointerEvents: 'none',
1587
+ animation: 'tour-fade-in 0.3s ease-out',
1588
+ }, "data-tour-blocking-indicator": true, children: "\uD83D\uDD12 Interactions blocked" })), jsxRuntimeExports.jsx("style", { children: `
1232
1589
  @keyframes tour-highlight-pulse {
1233
1590
  0%, 100% {
1234
1591
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.3);
@@ -1256,39 +1613,54 @@ function TourOverlay({ className }) {
1256
1613
  [data-tour-highlight] {
1257
1614
  animation: tour-fade-in 0.3s ease-out;
1258
1615
  }
1616
+
1617
+ [data-tour-blocking-indicator] {
1618
+ animation: tour-fade-in 0.3s ease-out;
1619
+ }
1259
1620
  ` })] }));
1260
- }
1621
+ });
1261
1622
 
1262
1623
  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}
1263
1624
 
1264
- function TourPopover({ className }) {
1265
- const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
1625
+ const TourPopover = React.memo(function TourPopover({ className }) {
1626
+ const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious, config } = useTour();
1266
1627
  const { targetElement } = useTourHighlight(state.currentStep);
1267
- const popoverRef = require$$0.useRef(null);
1268
- const [position, setPosition] = require$$0.useState({ top: 0, left: 0, placement: 'top' });
1269
- require$$0.useEffect(() => {
1270
- if (!popoverRef.current || !targetElement || !state.currentStep)
1628
+ const popoverRef = React.useRef(null);
1629
+ const [position, setPosition] = React.useState({ top: 0, left: 0, placement: 'top' });
1630
+ const updatePosition = React.useCallback(() => {
1631
+ if (!popoverRef.current || !state.currentStep)
1271
1632
  return;
1272
- const updatePosition = () => {
1273
- const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
1274
- setPosition(newPosition);
1275
- };
1276
- updatePosition();
1277
- const handleResize = () => updatePosition();
1278
- const handleScroll = () => updatePosition();
1279
- window.addEventListener('resize', handleResize);
1280
- window.addEventListener('scroll', handleScroll, { passive: true });
1281
- return () => {
1282
- window.removeEventListener('resize', handleResize);
1283
- window.removeEventListener('scroll', handleScroll);
1284
- };
1633
+ // For center placement without target element, calculate center position directly
1634
+ if (state.currentStep.placement === 'center' && !targetElement) {
1635
+ const popoverRect = popoverRef.current.getBoundingClientRect();
1636
+ const viewport = {
1637
+ width: window.innerWidth,
1638
+ height: window.innerHeight,
1639
+ scrollTop: window.pageYOffset || document.documentElement.scrollTop,
1640
+ scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
1641
+ };
1642
+ setPosition({
1643
+ top: viewport.scrollTop + (viewport.height - popoverRect.height) / 2,
1644
+ left: viewport.scrollLeft + (viewport.width - popoverRect.width) / 2,
1645
+ placement: 'center',
1646
+ });
1647
+ return;
1648
+ }
1649
+ // For other placements, require target element
1650
+ if (!targetElement)
1651
+ return;
1652
+ const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
1653
+ setPosition(newPosition);
1285
1654
  }, [targetElement, state.currentStep]);
1286
- if (!state.isRunning || !state.currentStep) {
1287
- return null;
1288
- }
1289
1655
  const step = state.currentStep;
1290
- const popoverConfig = step.popover || {};
1291
- const popoverStyle = {
1656
+ const popoverConfig = React.useMemo(() => step?.popover || {}, [step?.popover]);
1657
+ // Determine if click-to-advance is enabled for this step
1658
+ const shouldClickToAdvance = React.useMemo(() => {
1659
+ const stepClickToAdvance = step?.clickToAdvance;
1660
+ const globalClickToAdvance = config.clickToAdvance;
1661
+ return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
1662
+ }, [step?.clickToAdvance, config.clickToAdvance]);
1663
+ const popoverStyle = React.useMemo(() => ({
1292
1664
  position: 'absolute',
1293
1665
  top: position.top,
1294
1666
  left: position.left,
@@ -1313,23 +1685,41 @@ function TourPopover({ className }) {
1313
1685
  color: theme.textColor || '#1f2937',
1314
1686
  zIndex: theme.zIndex || 9999,
1315
1687
  animation: 'tour-fade-in 0.3s ease-out',
1316
- };
1317
- const handleNext = async () => {
1688
+ }), [position.top, position.left, theme]);
1689
+ const handleNext = React.useCallback(async () => {
1318
1690
  if (canGoNext || isLastStep) {
1319
1691
  await next();
1320
1692
  }
1321
- };
1322
- const handlePrevious = async () => {
1693
+ }, [canGoNext, isLastStep, next]);
1694
+ const handlePrevious = React.useCallback(async () => {
1323
1695
  if (canGoPrevious) {
1324
1696
  await previous();
1325
1697
  }
1326
- };
1327
- const handleSkip = async () => {
1698
+ }, [canGoPrevious, previous]);
1699
+ const handleSkip = React.useCallback(async () => {
1328
1700
  await skip();
1329
- };
1330
- const handleClose = async () => {
1701
+ }, [skip]);
1702
+ const handleClose = React.useCallback(async () => {
1331
1703
  await stop();
1332
- };
1704
+ }, [stop]);
1705
+ React.useEffect(() => {
1706
+ // Small delay to ensure popover is rendered and has dimensions
1707
+ const timeoutId = setTimeout(() => {
1708
+ updatePosition();
1709
+ }, 10);
1710
+ const handleResize = () => updatePosition();
1711
+ const handleScroll = () => updatePosition();
1712
+ window.addEventListener('resize', handleResize);
1713
+ window.addEventListener('scroll', handleScroll, { passive: true });
1714
+ return () => {
1715
+ clearTimeout(timeoutId);
1716
+ window.removeEventListener('resize', handleResize);
1717
+ window.removeEventListener('scroll', handleScroll);
1718
+ };
1719
+ }, [updatePosition]);
1720
+ if (!state.isRunning || !state.currentStep) {
1721
+ return null;
1722
+ }
1333
1723
  return (jsxRuntimeExports.jsxs("div", { ref: popoverRef, style: {
1334
1724
  ...popoverStyle,
1335
1725
  pointerEvents: 'auto',
@@ -1364,22 +1754,22 @@ function TourPopover({ className }) {
1364
1754
  borderRadius: '12px',
1365
1755
  fontSize: '12px',
1366
1756
  fontWeight: '500',
1367
- }, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step.title) && (jsxRuntimeExports.jsx("h3", { style: {
1757
+ }, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step?.title) && (jsxRuntimeExports.jsx("h3", { style: {
1368
1758
  margin: '0 0 8px 0',
1369
1759
  fontSize: '16px',
1370
1760
  fontWeight: '600',
1371
1761
  color: theme.textColor || '#1f2937',
1372
- }, children: popoverConfig.title || step.title })), jsxRuntimeExports.jsx("div", { style: {
1762
+ }, children: popoverConfig.title || step?.title })), jsxRuntimeExports.jsx("div", { style: {
1373
1763
  margin: '0 0 16px 0',
1374
1764
  lineHeight: '1.5',
1375
1765
  color: theme.textColor || '#374151',
1376
- }, children: popoverConfig.content || step.content })] }), jsxRuntimeExports.jsxs("div", { style: {
1766
+ }, children: popoverConfig.content || step?.content })] }), jsxRuntimeExports.jsxs("div", { style: {
1377
1767
  display: 'flex',
1378
1768
  justifyContent: 'space-between',
1379
1769
  alignItems: 'center',
1380
1770
  padding: '0 20px 20px 20px',
1381
1771
  gap: '12px',
1382
- }, children: [(popoverConfig.showSkip !== false && step.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
1772
+ }, children: [(popoverConfig.showSkip !== false && step?.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
1383
1773
  handleSkip();
1384
1774
  }, style: {
1385
1775
  background: 'none',
@@ -1419,7 +1809,7 @@ function TourPopover({ className }) {
1419
1809
  fontWeight: '500',
1420
1810
  opacity: canGoPrevious ? 1 : 0.5,
1421
1811
  transition: 'all 0.2s ease',
1422
- }, children: "Previous" })), jsxRuntimeExports.jsx("button", { onClick: (e) => {
1812
+ }, children: "Previous" })), !shouldClickToAdvance && (jsxRuntimeExports.jsx("button", { onClick: (e) => {
1423
1813
  e.preventDefault();
1424
1814
  e.stopPropagation();
1425
1815
  handleNext();
@@ -1442,7 +1832,18 @@ function TourPopover({ className }) {
1442
1832
  zIndex: 99999,
1443
1833
  }, children: isLastStep
1444
1834
  ? (popoverConfig.finishLabel || 'Finish')
1445
- : (popoverConfig.nextLabel || 'Next') })] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
1835
+ : (popoverConfig.nextLabel || 'Next') })), shouldClickToAdvance && (jsxRuntimeExports.jsx("div", { style: {
1836
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
1837
+ border: '1px solid rgba(59, 130, 246, 0.3)',
1838
+ color: theme.primaryColor || '#3b82f6',
1839
+ padding: '8px 16px',
1840
+ borderRadius: '8px',
1841
+ fontSize: '14px',
1842
+ fontWeight: '500',
1843
+ display: 'flex',
1844
+ alignItems: 'center',
1845
+ gap: '6px',
1846
+ }, children: "\uD83D\uDC46 Click the highlighted element to continue" }))] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
1446
1847
  position: 'absolute',
1447
1848
  width: '12px',
1448
1849
  height: '12px',
@@ -1462,7 +1863,7 @@ function TourPopover({ className }) {
1462
1863
  transform: 'rotate(45deg)',
1463
1864
  ...getArrowPosition(position.placement),
1464
1865
  }, "data-tour-arrow": true }))] }));
1465
- }
1866
+ });
1466
1867
  function getArrowPosition(placement) {
1467
1868
  switch (placement) {
1468
1869
  case 'top':
@@ -1502,13 +1903,92 @@ function getArrowPosition(placement) {
1502
1903
  }
1503
1904
  }
1504
1905
 
1505
- function TourRunner({ className }) {
1906
+ class ErrorBoundary extends React.Component {
1907
+ constructor(props) {
1908
+ super(props);
1909
+ this.retryTimeoutId = null;
1910
+ this.retry = () => {
1911
+ this.setState({
1912
+ hasError: false,
1913
+ error: null,
1914
+ errorInfo: null,
1915
+ });
1916
+ };
1917
+ this.state = {
1918
+ hasError: false,
1919
+ error: null,
1920
+ errorInfo: null,
1921
+ };
1922
+ }
1923
+ static getDerivedStateFromError(error) {
1924
+ return {
1925
+ hasError: true,
1926
+ error,
1927
+ };
1928
+ }
1929
+ componentDidCatch(error, errorInfo) {
1930
+ this.setState({
1931
+ error,
1932
+ errorInfo,
1933
+ });
1934
+ if (this.props.onError) {
1935
+ this.props.onError(error, errorInfo);
1936
+ }
1937
+ console.error('Tour ErrorBoundary caught an error:', error, errorInfo);
1938
+ }
1939
+ componentWillUnmount() {
1940
+ if (this.retryTimeoutId) {
1941
+ clearTimeout(this.retryTimeoutId);
1942
+ }
1943
+ }
1944
+ render() {
1945
+ if (this.state.hasError && this.state.error && this.state.errorInfo) {
1946
+ if (this.props.fallback) {
1947
+ return this.props.fallback(this.state.error, this.state.errorInfo, this.retry);
1948
+ }
1949
+ return (jsxRuntimeExports.jsxs("div", { style: {
1950
+ padding: '20px',
1951
+ margin: '20px',
1952
+ border: '2px solid #ff6b6b',
1953
+ borderRadius: '8px',
1954
+ backgroundColor: '#fff5f5',
1955
+ color: '#c92a2a',
1956
+ fontFamily: 'system-ui, sans-serif'
1957
+ }, 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: {
1958
+ marginTop: '10px',
1959
+ padding: '10px',
1960
+ backgroundColor: '#f8f8f8',
1961
+ borderRadius: '4px',
1962
+ fontSize: '12px',
1963
+ overflow: 'auto'
1964
+ }, 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: {
1965
+ padding: '8px 16px',
1966
+ backgroundColor: '#228be6',
1967
+ color: 'white',
1968
+ border: 'none',
1969
+ borderRadius: '4px',
1970
+ cursor: 'pointer',
1971
+ fontSize: '14px'
1972
+ }, children: "Try Again" })] }));
1973
+ }
1974
+ return this.props.children;
1975
+ }
1976
+ }
1977
+ function withErrorBoundary(Component, errorBoundaryProps) {
1978
+ const WrappedComponent = (props) => (jsxRuntimeExports.jsx(ErrorBoundary, { ...errorBoundaryProps, children: jsxRuntimeExports.jsx(Component, { ...props }) }));
1979
+ WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
1980
+ return WrappedComponent;
1981
+ }
1982
+
1983
+ const TourRunner = React.memo(function TourRunner({ className }) {
1506
1984
  const { state } = useTour();
1507
1985
  if (!state.isRunning) {
1508
1986
  return null;
1509
1987
  }
1510
- return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
1511
- }
1988
+ return (jsxRuntimeExports.jsxs(ErrorBoundary, { onError: (error, errorInfo) => {
1989
+ console.error('TourRunner error:', error, errorInfo);
1990
+ }, children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
1991
+ });
1512
1992
 
1513
1993
  class TabIntegration {
1514
1994
  constructor() {
@@ -1525,33 +2005,26 @@ class TabIntegration {
1525
2005
  if (!targetElement) {
1526
2006
  throw new Error(`Tab element not found: ${action.target}`);
1527
2007
  }
1528
- // Handle different tab implementations
1529
2008
  const tabValue = typeof action.value === 'string' ? action.value : undefined;
1530
2009
  await this.handleTabClick(targetElement, tabValue);
1531
2010
  }
1532
2011
  async handleTabClick(tabElement, tabValue) {
1533
- // Check for common tab patterns
1534
- // 1. Radix UI Tabs
1535
2012
  if (this.isRadixTab(tabElement)) {
1536
2013
  await this.handleRadixTab(tabElement);
1537
2014
  return;
1538
2015
  }
1539
- // 2. Material UI Tabs
1540
2016
  if (this.isMaterialUITab(tabElement)) {
1541
2017
  await this.handleMaterialUITab(tabElement);
1542
2018
  return;
1543
2019
  }
1544
- // 3. React Router tabs (links)
1545
2020
  if (this.isRouterTab(tabElement)) {
1546
2021
  await this.handleRouterTab(tabElement);
1547
2022
  return;
1548
2023
  }
1549
- // 4. Custom tabs with data attributes
1550
2024
  if (this.isCustomTab(tabElement)) {
1551
2025
  await this.handleCustomTab(tabElement, tabValue);
1552
2026
  return;
1553
2027
  }
1554
- // 5. Generic button/clickable tab
1555
2028
  await this.handleGenericTab(tabElement);
1556
2029
  }
1557
2030
  isRadixTab(element) {
@@ -2095,6 +2568,7 @@ class NavigationIntegration {
2095
2568
  }
2096
2569
  }
2097
2570
 
2571
+ exports.ErrorBoundary = ErrorBoundary;
2098
2572
  exports.NavigationIntegration = NavigationIntegration;
2099
2573
  exports.TabIntegration = TabIntegration;
2100
2574
  exports.TourActions = TourActions;
@@ -2106,11 +2580,15 @@ exports.TourRunner = TourRunner;
2106
2580
  exports.TourStorage = TourStorage;
2107
2581
  exports.WizardIntegration = WizardIntegration;
2108
2582
  exports.calculatePopoverPosition = calculatePopoverPosition;
2583
+ exports.createSafeAsyncOperation = createSafeAsyncOperation;
2109
2584
  exports.getElementPosition = getElementPosition;
2110
2585
  exports.getViewportCenter = getViewportCenter;
2111
2586
  exports.isElementInViewport = isElementInViewport;
2112
2587
  exports.scrollToElement = scrollToElement;
2588
+ exports.useErrorBoundary = useErrorBoundary;
2589
+ exports.useErrorHandler = useErrorHandler;
2113
2590
  exports.useTour = useTour;
2114
2591
  exports.useTourEngine = useTourEngine;
2115
2592
  exports.useTourHighlight = useTourHighlight;
2593
+ exports.withErrorBoundary = withErrorBoundary;
2116
2594
  //# sourceMappingURL=index.js.map