@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.
- package/README.md +11 -1
- package/dist/components/ErrorBoundary.d.ts +31 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/Toast.d.ts +14 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/ToastProvider.d.ts +28 -0
- package/dist/components/ToastProvider.d.ts.map +1 -0
- package/dist/components/TourOverlay.d.ts +2 -1
- package/dist/components/TourOverlay.d.ts.map +1 -1
- package/dist/components/TourPopover.d.ts +2 -1
- package/dist/components/TourPopover.d.ts.map +1 -1
- package/dist/components/TourProvider.d.ts +10 -2
- package/dist/components/TourProvider.d.ts.map +1 -1
- package/dist/components/TourRunner.d.ts +2 -1
- package/dist/components/TourRunner.d.ts.map +1 -1
- package/dist/core/TourActions.d.ts +10 -0
- package/dist/core/TourActions.d.ts.map +1 -1
- package/dist/core/TourEngine.d.ts +32 -0
- package/dist/core/TourEngine.d.ts.map +1 -1
- package/dist/hooks/useErrorHandler.d.ts +26 -0
- package/dist/hooks/useErrorHandler.d.ts.map +1 -0
- package/dist/hooks/useToastErrorHandler.d.ts +15 -0
- package/dist/hooks/useToastErrorHandler.d.ts.map +1 -0
- package/dist/hooks/useTourEngine.d.ts +4 -0
- package/dist/hooks/useTourEngine.d.ts.map +1 -1
- package/dist/hooks/useTourHighlight.d.ts +4 -0
- package/dist/hooks/useTourHighlight.d.ts.map +1 -1
- package/dist/index.d.ts +136 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +418 -137
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +442 -156
- package/dist/index.js.map +1 -1
- package/dist/integrations/TabIntegration.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/positioning.d.ts +13 -0
- package/dist/utils/positioning.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
167
|
+
throw new Error('No current step available for next operation');
|
|
153
168
|
}
|
|
154
|
-
// Execute after step hook
|
|
155
169
|
if (currentStep.afterStep) {
|
|
156
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
387
|
+
error: errorMessage,
|
|
333
388
|
isLoading: false,
|
|
334
389
|
});
|
|
335
|
-
this.emit('error', {
|
|
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 =
|
|
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
|
-
|
|
916
|
-
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
//
|
|
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
|
|
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;
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1476
|
+
const updatePosition = useCallback(() => {
|
|
1268
1477
|
if (!popoverRef.current || !targetElement || !state.currentStep)
|
|
1269
1478
|
return;
|
|
1270
|
-
const
|
|
1271
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|