@aladinbs/react-guided-tour 1.0.1 → 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 -11
- 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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
169
|
+
throw new Error('No current step available for next operation');
|
|
155
170
|
}
|
|
156
|
-
// Execute after step hook
|
|
157
171
|
if (currentStep.afterStep) {
|
|
158
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
389
|
+
error: errorMessage,
|
|
335
390
|
isLoading: false,
|
|
336
391
|
});
|
|
337
|
-
this.emit('error', {
|
|
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 =
|
|
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
|
-
|
|
907
|
-
const
|
|
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] =
|
|
912
|
-
|
|
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 =
|
|
917
|
-
|
|
918
|
-
|
|
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 =
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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;
|
|
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
|
-
//
|
|
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
|
|
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;
|
|
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] =
|
|
1104
|
-
const [highlightStyle, setHighlightStyle] =
|
|
1105
|
-
const [isVisible, setIsVisible] =
|
|
1106
|
-
const observerRef =
|
|
1107
|
-
|
|
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
|
-
//
|
|
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
|
|
1152
|
-
|
|
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,10 @@ function useTourHighlight(step) {
|
|
|
1206
1415
|
};
|
|
1207
1416
|
}
|
|
1208
1417
|
|
|
1209
|
-
function TourOverlay({ className }) {
|
|
1418
|
+
const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
1210
1419
|
const { state, theme, stop } = useTour();
|
|
1211
1420
|
const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
|
|
1212
|
-
|
|
1213
|
-
return null;
|
|
1214
|
-
}
|
|
1215
|
-
const overlayStyle = {
|
|
1421
|
+
const overlayStyle = React.useMemo(() => ({
|
|
1216
1422
|
position: 'fixed',
|
|
1217
1423
|
top: 0,
|
|
1218
1424
|
left: 0,
|
|
@@ -1222,12 +1428,15 @@ function TourOverlay({ className }) {
|
|
|
1222
1428
|
opacity: theme.overlay?.opacity || 0.5,
|
|
1223
1429
|
zIndex: (theme.zIndex || 9999) - 1,
|
|
1224
1430
|
pointerEvents: 'auto',
|
|
1225
|
-
};
|
|
1226
|
-
const handleOverlayClick = () => {
|
|
1431
|
+
}), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
|
|
1432
|
+
const handleOverlayClick = React.useCallback(() => {
|
|
1227
1433
|
if (state.currentStep?.canSkip !== false) {
|
|
1228
1434
|
stop();
|
|
1229
1435
|
}
|
|
1230
|
-
};
|
|
1436
|
+
}, [state.currentStep?.canSkip, stop]);
|
|
1437
|
+
if (!state.isRunning || !state.currentStep) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1231
1440
|
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: `
|
|
1232
1441
|
@keyframes tour-highlight-pulse {
|
|
1233
1442
|
0%, 100% {
|
|
@@ -1257,38 +1466,24 @@ function TourOverlay({ className }) {
|
|
|
1257
1466
|
animation: tour-fade-in 0.3s ease-out;
|
|
1258
1467
|
}
|
|
1259
1468
|
` })] }));
|
|
1260
|
-
}
|
|
1469
|
+
});
|
|
1261
1470
|
|
|
1262
1471
|
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
1472
|
|
|
1264
|
-
function TourPopover({ className }) {
|
|
1473
|
+
const TourPopover = React.memo(function TourPopover({ className }) {
|
|
1265
1474
|
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
|
|
1266
1475
|
const { targetElement } = useTourHighlight(state.currentStep);
|
|
1267
|
-
const popoverRef =
|
|
1268
|
-
const [position, setPosition] =
|
|
1269
|
-
|
|
1476
|
+
const popoverRef = React.useRef(null);
|
|
1477
|
+
const [position, setPosition] = React.useState({ top: 0, left: 0, placement: 'top' });
|
|
1478
|
+
const updatePosition = React.useCallback(() => {
|
|
1270
1479
|
if (!popoverRef.current || !targetElement || !state.currentStep)
|
|
1271
1480
|
return;
|
|
1272
|
-
const
|
|
1273
|
-
|
|
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
|
-
};
|
|
1481
|
+
const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
|
|
1482
|
+
setPosition(newPosition);
|
|
1285
1483
|
}, [targetElement, state.currentStep]);
|
|
1286
|
-
if (!state.isRunning || !state.currentStep) {
|
|
1287
|
-
return null;
|
|
1288
|
-
}
|
|
1289
1484
|
const step = state.currentStep;
|
|
1290
|
-
const popoverConfig = step
|
|
1291
|
-
const popoverStyle = {
|
|
1485
|
+
const popoverConfig = React.useMemo(() => step?.popover || {}, [step?.popover]);
|
|
1486
|
+
const popoverStyle = React.useMemo(() => ({
|
|
1292
1487
|
position: 'absolute',
|
|
1293
1488
|
top: position.top,
|
|
1294
1489
|
left: position.left,
|
|
@@ -1313,23 +1508,37 @@ function TourPopover({ className }) {
|
|
|
1313
1508
|
color: theme.textColor || '#1f2937',
|
|
1314
1509
|
zIndex: theme.zIndex || 9999,
|
|
1315
1510
|
animation: 'tour-fade-in 0.3s ease-out',
|
|
1316
|
-
};
|
|
1317
|
-
const handleNext = async () => {
|
|
1511
|
+
}), [position.top, position.left, theme]);
|
|
1512
|
+
const handleNext = React.useCallback(async () => {
|
|
1318
1513
|
if (canGoNext || isLastStep) {
|
|
1319
1514
|
await next();
|
|
1320
1515
|
}
|
|
1321
|
-
};
|
|
1322
|
-
const handlePrevious = async () => {
|
|
1516
|
+
}, [canGoNext, isLastStep, next]);
|
|
1517
|
+
const handlePrevious = React.useCallback(async () => {
|
|
1323
1518
|
if (canGoPrevious) {
|
|
1324
1519
|
await previous();
|
|
1325
1520
|
}
|
|
1326
|
-
};
|
|
1327
|
-
const handleSkip = async () => {
|
|
1521
|
+
}, [canGoPrevious, previous]);
|
|
1522
|
+
const handleSkip = React.useCallback(async () => {
|
|
1328
1523
|
await skip();
|
|
1329
|
-
};
|
|
1330
|
-
const handleClose = async () => {
|
|
1524
|
+
}, [skip]);
|
|
1525
|
+
const handleClose = React.useCallback(async () => {
|
|
1331
1526
|
await stop();
|
|
1332
|
-
};
|
|
1527
|
+
}, [stop]);
|
|
1528
|
+
React.useEffect(() => {
|
|
1529
|
+
updatePosition();
|
|
1530
|
+
const handleResize = () => updatePosition();
|
|
1531
|
+
const handleScroll = () => updatePosition();
|
|
1532
|
+
window.addEventListener('resize', handleResize);
|
|
1533
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1534
|
+
return () => {
|
|
1535
|
+
window.removeEventListener('resize', handleResize);
|
|
1536
|
+
window.removeEventListener('scroll', handleScroll);
|
|
1537
|
+
};
|
|
1538
|
+
}, [updatePosition]);
|
|
1539
|
+
if (!state.isRunning || !state.currentStep) {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1333
1542
|
return (jsxRuntimeExports.jsxs("div", { ref: popoverRef, style: {
|
|
1334
1543
|
...popoverStyle,
|
|
1335
1544
|
pointerEvents: 'auto',
|
|
@@ -1364,22 +1573,22 @@ function TourPopover({ className }) {
|
|
|
1364
1573
|
borderRadius: '12px',
|
|
1365
1574
|
fontSize: '12px',
|
|
1366
1575
|
fontWeight: '500',
|
|
1367
|
-
}, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step
|
|
1576
|
+
}, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step?.title) && (jsxRuntimeExports.jsx("h3", { style: {
|
|
1368
1577
|
margin: '0 0 8px 0',
|
|
1369
1578
|
fontSize: '16px',
|
|
1370
1579
|
fontWeight: '600',
|
|
1371
1580
|
color: theme.textColor || '#1f2937',
|
|
1372
|
-
}, children: popoverConfig.title || step
|
|
1581
|
+
}, children: popoverConfig.title || step?.title })), jsxRuntimeExports.jsx("div", { style: {
|
|
1373
1582
|
margin: '0 0 16px 0',
|
|
1374
1583
|
lineHeight: '1.5',
|
|
1375
1584
|
color: theme.textColor || '#374151',
|
|
1376
|
-
}, children: popoverConfig.content || step
|
|
1585
|
+
}, children: popoverConfig.content || step?.content })] }), jsxRuntimeExports.jsxs("div", { style: {
|
|
1377
1586
|
display: 'flex',
|
|
1378
1587
|
justifyContent: 'space-between',
|
|
1379
1588
|
alignItems: 'center',
|
|
1380
1589
|
padding: '0 20px 20px 20px',
|
|
1381
1590
|
gap: '12px',
|
|
1382
|
-
}, children: [(popoverConfig.showSkip !== false && step
|
|
1591
|
+
}, children: [(popoverConfig.showSkip !== false && step?.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
|
|
1383
1592
|
handleSkip();
|
|
1384
1593
|
}, style: {
|
|
1385
1594
|
background: 'none',
|
|
@@ -1462,7 +1671,7 @@ function TourPopover({ className }) {
|
|
|
1462
1671
|
transform: 'rotate(45deg)',
|
|
1463
1672
|
...getArrowPosition(position.placement),
|
|
1464
1673
|
}, "data-tour-arrow": true }))] }));
|
|
1465
|
-
}
|
|
1674
|
+
});
|
|
1466
1675
|
function getArrowPosition(placement) {
|
|
1467
1676
|
switch (placement) {
|
|
1468
1677
|
case 'top':
|
|
@@ -1502,13 +1711,92 @@ function getArrowPosition(placement) {
|
|
|
1502
1711
|
}
|
|
1503
1712
|
}
|
|
1504
1713
|
|
|
1505
|
-
|
|
1714
|
+
class ErrorBoundary extends React.Component {
|
|
1715
|
+
constructor(props) {
|
|
1716
|
+
super(props);
|
|
1717
|
+
this.retryTimeoutId = null;
|
|
1718
|
+
this.retry = () => {
|
|
1719
|
+
this.setState({
|
|
1720
|
+
hasError: false,
|
|
1721
|
+
error: null,
|
|
1722
|
+
errorInfo: null,
|
|
1723
|
+
});
|
|
1724
|
+
};
|
|
1725
|
+
this.state = {
|
|
1726
|
+
hasError: false,
|
|
1727
|
+
error: null,
|
|
1728
|
+
errorInfo: null,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
static getDerivedStateFromError(error) {
|
|
1732
|
+
return {
|
|
1733
|
+
hasError: true,
|
|
1734
|
+
error,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
componentDidCatch(error, errorInfo) {
|
|
1738
|
+
this.setState({
|
|
1739
|
+
error,
|
|
1740
|
+
errorInfo,
|
|
1741
|
+
});
|
|
1742
|
+
if (this.props.onError) {
|
|
1743
|
+
this.props.onError(error, errorInfo);
|
|
1744
|
+
}
|
|
1745
|
+
console.error('Tour ErrorBoundary caught an error:', error, errorInfo);
|
|
1746
|
+
}
|
|
1747
|
+
componentWillUnmount() {
|
|
1748
|
+
if (this.retryTimeoutId) {
|
|
1749
|
+
clearTimeout(this.retryTimeoutId);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
render() {
|
|
1753
|
+
if (this.state.hasError && this.state.error && this.state.errorInfo) {
|
|
1754
|
+
if (this.props.fallback) {
|
|
1755
|
+
return this.props.fallback(this.state.error, this.state.errorInfo, this.retry);
|
|
1756
|
+
}
|
|
1757
|
+
return (jsxRuntimeExports.jsxs("div", { style: {
|
|
1758
|
+
padding: '20px',
|
|
1759
|
+
margin: '20px',
|
|
1760
|
+
border: '2px solid #ff6b6b',
|
|
1761
|
+
borderRadius: '8px',
|
|
1762
|
+
backgroundColor: '#fff5f5',
|
|
1763
|
+
color: '#c92a2a',
|
|
1764
|
+
fontFamily: 'system-ui, sans-serif'
|
|
1765
|
+
}, 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: {
|
|
1766
|
+
marginTop: '10px',
|
|
1767
|
+
padding: '10px',
|
|
1768
|
+
backgroundColor: '#f8f8f8',
|
|
1769
|
+
borderRadius: '4px',
|
|
1770
|
+
fontSize: '12px',
|
|
1771
|
+
overflow: 'auto'
|
|
1772
|
+
}, 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: {
|
|
1773
|
+
padding: '8px 16px',
|
|
1774
|
+
backgroundColor: '#228be6',
|
|
1775
|
+
color: 'white',
|
|
1776
|
+
border: 'none',
|
|
1777
|
+
borderRadius: '4px',
|
|
1778
|
+
cursor: 'pointer',
|
|
1779
|
+
fontSize: '14px'
|
|
1780
|
+
}, children: "Try Again" })] }));
|
|
1781
|
+
}
|
|
1782
|
+
return this.props.children;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function withErrorBoundary(Component, errorBoundaryProps) {
|
|
1786
|
+
const WrappedComponent = (props) => (jsxRuntimeExports.jsx(ErrorBoundary, { ...errorBoundaryProps, children: jsxRuntimeExports.jsx(Component, { ...props }) }));
|
|
1787
|
+
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
|
|
1788
|
+
return WrappedComponent;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const TourRunner = React.memo(function TourRunner({ className }) {
|
|
1506
1792
|
const { state } = useTour();
|
|
1507
1793
|
if (!state.isRunning) {
|
|
1508
1794
|
return null;
|
|
1509
1795
|
}
|
|
1510
|
-
return (jsxRuntimeExports.jsxs(
|
|
1511
|
-
|
|
1796
|
+
return (jsxRuntimeExports.jsxs(ErrorBoundary, { onError: (error, errorInfo) => {
|
|
1797
|
+
console.error('TourRunner error:', error, errorInfo);
|
|
1798
|
+
}, children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
|
|
1799
|
+
});
|
|
1512
1800
|
|
|
1513
1801
|
class TabIntegration {
|
|
1514
1802
|
constructor() {
|
|
@@ -1525,33 +1813,26 @@ class TabIntegration {
|
|
|
1525
1813
|
if (!targetElement) {
|
|
1526
1814
|
throw new Error(`Tab element not found: ${action.target}`);
|
|
1527
1815
|
}
|
|
1528
|
-
// Handle different tab implementations
|
|
1529
1816
|
const tabValue = typeof action.value === 'string' ? action.value : undefined;
|
|
1530
1817
|
await this.handleTabClick(targetElement, tabValue);
|
|
1531
1818
|
}
|
|
1532
1819
|
async handleTabClick(tabElement, tabValue) {
|
|
1533
|
-
// Check for common tab patterns
|
|
1534
|
-
// 1. Radix UI Tabs
|
|
1535
1820
|
if (this.isRadixTab(tabElement)) {
|
|
1536
1821
|
await this.handleRadixTab(tabElement);
|
|
1537
1822
|
return;
|
|
1538
1823
|
}
|
|
1539
|
-
// 2. Material UI Tabs
|
|
1540
1824
|
if (this.isMaterialUITab(tabElement)) {
|
|
1541
1825
|
await this.handleMaterialUITab(tabElement);
|
|
1542
1826
|
return;
|
|
1543
1827
|
}
|
|
1544
|
-
// 3. React Router tabs (links)
|
|
1545
1828
|
if (this.isRouterTab(tabElement)) {
|
|
1546
1829
|
await this.handleRouterTab(tabElement);
|
|
1547
1830
|
return;
|
|
1548
1831
|
}
|
|
1549
|
-
// 4. Custom tabs with data attributes
|
|
1550
1832
|
if (this.isCustomTab(tabElement)) {
|
|
1551
1833
|
await this.handleCustomTab(tabElement, tabValue);
|
|
1552
1834
|
return;
|
|
1553
1835
|
}
|
|
1554
|
-
// 5. Generic button/clickable tab
|
|
1555
1836
|
await this.handleGenericTab(tabElement);
|
|
1556
1837
|
}
|
|
1557
1838
|
isRadixTab(element) {
|
|
@@ -2095,6 +2376,7 @@ class NavigationIntegration {
|
|
|
2095
2376
|
}
|
|
2096
2377
|
}
|
|
2097
2378
|
|
|
2379
|
+
exports.ErrorBoundary = ErrorBoundary;
|
|
2098
2380
|
exports.NavigationIntegration = NavigationIntegration;
|
|
2099
2381
|
exports.TabIntegration = TabIntegration;
|
|
2100
2382
|
exports.TourActions = TourActions;
|
|
@@ -2106,11 +2388,15 @@ exports.TourRunner = TourRunner;
|
|
|
2106
2388
|
exports.TourStorage = TourStorage;
|
|
2107
2389
|
exports.WizardIntegration = WizardIntegration;
|
|
2108
2390
|
exports.calculatePopoverPosition = calculatePopoverPosition;
|
|
2391
|
+
exports.createSafeAsyncOperation = createSafeAsyncOperation;
|
|
2109
2392
|
exports.getElementPosition = getElementPosition;
|
|
2110
2393
|
exports.getViewportCenter = getViewportCenter;
|
|
2111
2394
|
exports.isElementInViewport = isElementInViewport;
|
|
2112
2395
|
exports.scrollToElement = scrollToElement;
|
|
2396
|
+
exports.useErrorBoundary = useErrorBoundary;
|
|
2397
|
+
exports.useErrorHandler = useErrorHandler;
|
|
2113
2398
|
exports.useTour = useTour;
|
|
2114
2399
|
exports.useTourEngine = useTourEngine;
|
|
2115
2400
|
exports.useTourHighlight = useTourHighlight;
|
|
2401
|
+
exports.withErrorBoundary = withErrorBoundary;
|
|
2116
2402
|
//# sourceMappingURL=index.js.map
|