@aladinbs/react-guided-tour 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -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 +140 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +617 -144
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +641 -163
- package/dist/index.js.map +1 -1
- package/dist/integrations/TabIntegration.d.ts.map +1 -1
- package/dist/types/index.d.ts +7 -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 -2
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,23 @@ function useTourHighlight(step) {
|
|
|
1204
1413
|
};
|
|
1205
1414
|
}
|
|
1206
1415
|
|
|
1207
|
-
function TourOverlay({ className }) {
|
|
1208
|
-
const { state, theme, stop } = useTour();
|
|
1416
|
+
const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
1417
|
+
const { state, theme, stop, config, next } = useTour();
|
|
1209
1418
|
const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1419
|
+
const [scrollTrigger, setScrollTrigger] = useState(0);
|
|
1420
|
+
// Determine if interactions should be blocked
|
|
1421
|
+
const shouldBlockInteractions = useMemo(() => {
|
|
1422
|
+
const stepBlocking = state.currentStep?.blockInteractions;
|
|
1423
|
+
const globalBlocking = config.blockInteractions;
|
|
1424
|
+
return stepBlocking !== undefined ? stepBlocking : globalBlocking;
|
|
1425
|
+
}, [state.currentStep?.blockInteractions, config.blockInteractions]);
|
|
1426
|
+
// Determine if click-to-advance is enabled
|
|
1427
|
+
const shouldClickToAdvance = useMemo(() => {
|
|
1428
|
+
const stepClickToAdvance = state.currentStep?.clickToAdvance;
|
|
1429
|
+
const globalClickToAdvance = config.clickToAdvance;
|
|
1430
|
+
return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
|
|
1431
|
+
}, [state.currentStep?.clickToAdvance, config.clickToAdvance]);
|
|
1432
|
+
const overlayStyle = useMemo(() => ({
|
|
1214
1433
|
position: 'fixed',
|
|
1215
1434
|
top: 0,
|
|
1216
1435
|
left: 0,
|
|
@@ -1220,13 +1439,151 @@ function TourOverlay({ className }) {
|
|
|
1220
1439
|
opacity: theme.overlay?.opacity || 0.5,
|
|
1221
1440
|
zIndex: (theme.zIndex || 9999) - 1,
|
|
1222
1441
|
pointerEvents: 'auto',
|
|
1223
|
-
};
|
|
1224
|
-
|
|
1225
|
-
|
|
1442
|
+
}), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
|
|
1443
|
+
// Create cutout style for the highlighted element
|
|
1444
|
+
const cutoutOverlayStyle = useMemo(() => {
|
|
1445
|
+
if ((!shouldBlockInteractions && !shouldClickToAdvance) || !targetElement || !isVisible) {
|
|
1446
|
+
return overlayStyle;
|
|
1447
|
+
}
|
|
1448
|
+
// Use getBoundingClientRect for viewport-relative coordinates (perfect for fixed overlay)
|
|
1449
|
+
const rect = targetElement.getBoundingClientRect();
|
|
1450
|
+
const padding = state.currentStep?.highlight?.padding || 8;
|
|
1451
|
+
// Ensure coordinates are within viewport bounds
|
|
1452
|
+
const left = Math.max(0, rect.left - padding);
|
|
1453
|
+
const top = Math.max(0, rect.top - padding);
|
|
1454
|
+
const right = Math.min(window.innerWidth, rect.right + padding);
|
|
1455
|
+
const bottom = Math.min(window.innerHeight, rect.bottom + padding);
|
|
1456
|
+
return {
|
|
1457
|
+
...overlayStyle,
|
|
1458
|
+
clipPath: `polygon(
|
|
1459
|
+
0% 0%,
|
|
1460
|
+
0% 100%,
|
|
1461
|
+
${left}px 100%,
|
|
1462
|
+
${left}px ${top}px,
|
|
1463
|
+
${right}px ${top}px,
|
|
1464
|
+
${right}px ${bottom}px,
|
|
1465
|
+
${left}px ${bottom}px,
|
|
1466
|
+
${left}px 100%,
|
|
1467
|
+
100% 100%,
|
|
1468
|
+
100% 0%
|
|
1469
|
+
)`,
|
|
1470
|
+
};
|
|
1471
|
+
}, [overlayStyle, shouldBlockInteractions, shouldClickToAdvance, targetElement, isVisible, state.currentStep?.highlight?.padding, scrollTrigger]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1472
|
+
// Force re-render of cutout overlay on scroll to maintain proper positioning
|
|
1473
|
+
useEffect(() => {
|
|
1474
|
+
if (!shouldBlockInteractions && !shouldClickToAdvance) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const handleScroll = () => {
|
|
1478
|
+
// Force recalculation by updating scroll trigger
|
|
1479
|
+
setScrollTrigger(prev => prev + 1);
|
|
1480
|
+
};
|
|
1481
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1482
|
+
window.addEventListener('resize', handleScroll, { passive: true });
|
|
1483
|
+
return () => {
|
|
1484
|
+
window.removeEventListener('scroll', handleScroll);
|
|
1485
|
+
window.removeEventListener('resize', handleScroll);
|
|
1486
|
+
};
|
|
1487
|
+
}, [shouldBlockInteractions, shouldClickToAdvance]);
|
|
1488
|
+
const handleOverlayClick = useCallback((e) => {
|
|
1489
|
+
// If interactions are blocked, prevent the click from propagating
|
|
1490
|
+
if (shouldBlockInteractions) {
|
|
1491
|
+
e.preventDefault();
|
|
1492
|
+
e.stopPropagation();
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
// Allow clicking outside to close tour if not blocking interactions
|
|
1496
|
+
if (config.allowClickOutside !== false && state.currentStep?.canSkip !== false) {
|
|
1226
1497
|
stop();
|
|
1227
1498
|
}
|
|
1228
|
-
};
|
|
1229
|
-
|
|
1499
|
+
}, [shouldBlockInteractions, config.allowClickOutside, state.currentStep?.canSkip, stop]);
|
|
1500
|
+
// Handle click-to-advance functionality
|
|
1501
|
+
useEffect(() => {
|
|
1502
|
+
if (!shouldClickToAdvance || !targetElement || !state.isRunning) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
// Ensure target element is above the overlay when click-to-advance is enabled
|
|
1506
|
+
const originalZIndex = targetElement.style.zIndex;
|
|
1507
|
+
const originalPosition = targetElement.style.position;
|
|
1508
|
+
targetElement.style.position = targetElement.style.position || 'relative';
|
|
1509
|
+
targetElement.style.zIndex = String((theme.zIndex || 9999) + 1);
|
|
1510
|
+
const handleTargetClick = (_e) => {
|
|
1511
|
+
// Don't prevent default or stop propagation - let the element's normal behavior work
|
|
1512
|
+
// Just advance the tour after a small delay to allow the click to be processed
|
|
1513
|
+
setTimeout(() => {
|
|
1514
|
+
next();
|
|
1515
|
+
}, 100);
|
|
1516
|
+
};
|
|
1517
|
+
targetElement.addEventListener('click', handleTargetClick, true);
|
|
1518
|
+
return () => {
|
|
1519
|
+
targetElement.removeEventListener('click', handleTargetClick, true);
|
|
1520
|
+
// Restore original styles
|
|
1521
|
+
targetElement.style.zIndex = originalZIndex;
|
|
1522
|
+
targetElement.style.position = originalPosition;
|
|
1523
|
+
};
|
|
1524
|
+
}, [shouldClickToAdvance, targetElement, state.isRunning, next, theme.zIndex]);
|
|
1525
|
+
// Block interactions on the entire page when blocking is enabled
|
|
1526
|
+
useEffect(() => {
|
|
1527
|
+
if (!shouldBlockInteractions || !state.isRunning) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const handleGlobalClick = (e) => {
|
|
1531
|
+
const target = e.target;
|
|
1532
|
+
// Allow clicks on the tour target element and its children
|
|
1533
|
+
if (targetElement && (targetElement.contains(target) || targetElement === target)) {
|
|
1534
|
+
// If click-to-advance is enabled, let the target click handler deal with it
|
|
1535
|
+
if (shouldClickToAdvance) {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
// Allow clicks on tour UI elements (popover, buttons, etc.)
|
|
1541
|
+
if (target.closest('[data-tour-popover]') ||
|
|
1542
|
+
target.closest('[data-tour-highlight]') ||
|
|
1543
|
+
target.closest('[data-tour-overlay]')) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
// Block all other clicks
|
|
1547
|
+
e.preventDefault();
|
|
1548
|
+
e.stopPropagation();
|
|
1549
|
+
};
|
|
1550
|
+
const handleGlobalKeydown = (e) => {
|
|
1551
|
+
// Allow tour navigation keys
|
|
1552
|
+
if (['Escape', 'ArrowLeft', 'ArrowRight', 'Enter', 'Space'].includes(e.key)) {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
// Block other keyboard interactions
|
|
1556
|
+
e.preventDefault();
|
|
1557
|
+
e.stopPropagation();
|
|
1558
|
+
};
|
|
1559
|
+
// Add event listeners to capture phase to block interactions early
|
|
1560
|
+
document.addEventListener('click', handleGlobalClick, true);
|
|
1561
|
+
document.addEventListener('keydown', handleGlobalKeydown, true);
|
|
1562
|
+
document.addEventListener('mousedown', handleGlobalClick, true);
|
|
1563
|
+
document.addEventListener('touchstart', handleGlobalClick, true);
|
|
1564
|
+
return () => {
|
|
1565
|
+
document.removeEventListener('click', handleGlobalClick, true);
|
|
1566
|
+
document.removeEventListener('keydown', handleGlobalKeydown, true);
|
|
1567
|
+
document.removeEventListener('mousedown', handleGlobalClick, true);
|
|
1568
|
+
document.removeEventListener('touchstart', handleGlobalClick, true);
|
|
1569
|
+
};
|
|
1570
|
+
}, [shouldBlockInteractions, state.isRunning, targetElement, shouldClickToAdvance]);
|
|
1571
|
+
if (!state.isRunning || !state.currentStep) {
|
|
1572
|
+
return null;
|
|
1573
|
+
}
|
|
1574
|
+
return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: (shouldBlockInteractions || shouldClickToAdvance) ? cutoutOverlayStyle : overlayStyle, onClick: handleOverlayClick, className: className, "data-tour-overlay": true }), isVisible && targetElement && (jsxRuntimeExports.jsx("div", { style: highlightStyle, "data-tour-highlight": true })), shouldBlockInteractions && (jsxRuntimeExports.jsx("div", { style: {
|
|
1575
|
+
position: 'fixed',
|
|
1576
|
+
top: '20px',
|
|
1577
|
+
right: '20px',
|
|
1578
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
1579
|
+
color: 'white',
|
|
1580
|
+
padding: '8px 12px',
|
|
1581
|
+
borderRadius: '4px',
|
|
1582
|
+
fontSize: '12px',
|
|
1583
|
+
zIndex: (theme.zIndex || 9999) + 1,
|
|
1584
|
+
pointerEvents: 'none',
|
|
1585
|
+
animation: 'tour-fade-in 0.3s ease-out',
|
|
1586
|
+
}, "data-tour-blocking-indicator": true, children: "\uD83D\uDD12 Interactions blocked" })), jsxRuntimeExports.jsx("style", { children: `
|
|
1230
1587
|
@keyframes tour-highlight-pulse {
|
|
1231
1588
|
0%, 100% {
|
|
1232
1589
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.3);
|
|
@@ -1254,39 +1611,54 @@ function TourOverlay({ className }) {
|
|
|
1254
1611
|
[data-tour-highlight] {
|
|
1255
1612
|
animation: tour-fade-in 0.3s ease-out;
|
|
1256
1613
|
}
|
|
1614
|
+
|
|
1615
|
+
[data-tour-blocking-indicator] {
|
|
1616
|
+
animation: tour-fade-in 0.3s ease-out;
|
|
1617
|
+
}
|
|
1257
1618
|
` })] }));
|
|
1258
|
-
}
|
|
1619
|
+
});
|
|
1259
1620
|
|
|
1260
1621
|
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
1622
|
|
|
1262
|
-
function TourPopover({ className }) {
|
|
1263
|
-
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
|
|
1623
|
+
const TourPopover = React.memo(function TourPopover({ className }) {
|
|
1624
|
+
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious, config } = useTour();
|
|
1264
1625
|
const { targetElement } = useTourHighlight(state.currentStep);
|
|
1265
1626
|
const popoverRef = useRef(null);
|
|
1266
1627
|
const [position, setPosition] = useState({ top: 0, left: 0, placement: 'top' });
|
|
1267
|
-
|
|
1268
|
-
if (!popoverRef.current || !
|
|
1628
|
+
const updatePosition = useCallback(() => {
|
|
1629
|
+
if (!popoverRef.current || !state.currentStep)
|
|
1269
1630
|
return;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1631
|
+
// For center placement without target element, calculate center position directly
|
|
1632
|
+
if (state.currentStep.placement === 'center' && !targetElement) {
|
|
1633
|
+
const popoverRect = popoverRef.current.getBoundingClientRect();
|
|
1634
|
+
const viewport = {
|
|
1635
|
+
width: window.innerWidth,
|
|
1636
|
+
height: window.innerHeight,
|
|
1637
|
+
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
|
|
1638
|
+
scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
|
|
1639
|
+
};
|
|
1640
|
+
setPosition({
|
|
1641
|
+
top: viewport.scrollTop + (viewport.height - popoverRect.height) / 2,
|
|
1642
|
+
left: viewport.scrollLeft + (viewport.width - popoverRect.width) / 2,
|
|
1643
|
+
placement: 'center',
|
|
1644
|
+
});
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
// For other placements, require target element
|
|
1648
|
+
if (!targetElement)
|
|
1649
|
+
return;
|
|
1650
|
+
const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
|
|
1651
|
+
setPosition(newPosition);
|
|
1283
1652
|
}, [targetElement, state.currentStep]);
|
|
1284
|
-
if (!state.isRunning || !state.currentStep) {
|
|
1285
|
-
return null;
|
|
1286
|
-
}
|
|
1287
1653
|
const step = state.currentStep;
|
|
1288
|
-
const popoverConfig = step
|
|
1289
|
-
|
|
1654
|
+
const popoverConfig = useMemo(() => step?.popover || {}, [step?.popover]);
|
|
1655
|
+
// Determine if click-to-advance is enabled for this step
|
|
1656
|
+
const shouldClickToAdvance = useMemo(() => {
|
|
1657
|
+
const stepClickToAdvance = step?.clickToAdvance;
|
|
1658
|
+
const globalClickToAdvance = config.clickToAdvance;
|
|
1659
|
+
return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
|
|
1660
|
+
}, [step?.clickToAdvance, config.clickToAdvance]);
|
|
1661
|
+
const popoverStyle = useMemo(() => ({
|
|
1290
1662
|
position: 'absolute',
|
|
1291
1663
|
top: position.top,
|
|
1292
1664
|
left: position.left,
|
|
@@ -1311,23 +1683,41 @@ function TourPopover({ className }) {
|
|
|
1311
1683
|
color: theme.textColor || '#1f2937',
|
|
1312
1684
|
zIndex: theme.zIndex || 9999,
|
|
1313
1685
|
animation: 'tour-fade-in 0.3s ease-out',
|
|
1314
|
-
};
|
|
1315
|
-
const handleNext = async () => {
|
|
1686
|
+
}), [position.top, position.left, theme]);
|
|
1687
|
+
const handleNext = useCallback(async () => {
|
|
1316
1688
|
if (canGoNext || isLastStep) {
|
|
1317
1689
|
await next();
|
|
1318
1690
|
}
|
|
1319
|
-
};
|
|
1320
|
-
const handlePrevious = async () => {
|
|
1691
|
+
}, [canGoNext, isLastStep, next]);
|
|
1692
|
+
const handlePrevious = useCallback(async () => {
|
|
1321
1693
|
if (canGoPrevious) {
|
|
1322
1694
|
await previous();
|
|
1323
1695
|
}
|
|
1324
|
-
};
|
|
1325
|
-
const handleSkip = async () => {
|
|
1696
|
+
}, [canGoPrevious, previous]);
|
|
1697
|
+
const handleSkip = useCallback(async () => {
|
|
1326
1698
|
await skip();
|
|
1327
|
-
};
|
|
1328
|
-
const handleClose = async () => {
|
|
1699
|
+
}, [skip]);
|
|
1700
|
+
const handleClose = useCallback(async () => {
|
|
1329
1701
|
await stop();
|
|
1330
|
-
};
|
|
1702
|
+
}, [stop]);
|
|
1703
|
+
useEffect(() => {
|
|
1704
|
+
// Small delay to ensure popover is rendered and has dimensions
|
|
1705
|
+
const timeoutId = setTimeout(() => {
|
|
1706
|
+
updatePosition();
|
|
1707
|
+
}, 10);
|
|
1708
|
+
const handleResize = () => updatePosition();
|
|
1709
|
+
const handleScroll = () => updatePosition();
|
|
1710
|
+
window.addEventListener('resize', handleResize);
|
|
1711
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1712
|
+
return () => {
|
|
1713
|
+
clearTimeout(timeoutId);
|
|
1714
|
+
window.removeEventListener('resize', handleResize);
|
|
1715
|
+
window.removeEventListener('scroll', handleScroll);
|
|
1716
|
+
};
|
|
1717
|
+
}, [updatePosition]);
|
|
1718
|
+
if (!state.isRunning || !state.currentStep) {
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1331
1721
|
return (jsxRuntimeExports.jsxs("div", { ref: popoverRef, style: {
|
|
1332
1722
|
...popoverStyle,
|
|
1333
1723
|
pointerEvents: 'auto',
|
|
@@ -1362,22 +1752,22 @@ function TourPopover({ className }) {
|
|
|
1362
1752
|
borderRadius: '12px',
|
|
1363
1753
|
fontSize: '12px',
|
|
1364
1754
|
fontWeight: '500',
|
|
1365
|
-
}, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step
|
|
1755
|
+
}, children: [state.currentStepIndex + 1, " of ", state.totalSteps] }))] }), jsxRuntimeExports.jsxs("div", { style: { padding: '16px 20px' }, children: [(popoverConfig.title || step?.title) && (jsxRuntimeExports.jsx("h3", { style: {
|
|
1366
1756
|
margin: '0 0 8px 0',
|
|
1367
1757
|
fontSize: '16px',
|
|
1368
1758
|
fontWeight: '600',
|
|
1369
1759
|
color: theme.textColor || '#1f2937',
|
|
1370
|
-
}, children: popoverConfig.title || step
|
|
1760
|
+
}, children: popoverConfig.title || step?.title })), jsxRuntimeExports.jsx("div", { style: {
|
|
1371
1761
|
margin: '0 0 16px 0',
|
|
1372
1762
|
lineHeight: '1.5',
|
|
1373
1763
|
color: theme.textColor || '#374151',
|
|
1374
|
-
}, children: popoverConfig.content || step
|
|
1764
|
+
}, children: popoverConfig.content || step?.content })] }), jsxRuntimeExports.jsxs("div", { style: {
|
|
1375
1765
|
display: 'flex',
|
|
1376
1766
|
justifyContent: 'space-between',
|
|
1377
1767
|
alignItems: 'center',
|
|
1378
1768
|
padding: '0 20px 20px 20px',
|
|
1379
1769
|
gap: '12px',
|
|
1380
|
-
}, children: [(popoverConfig.showSkip !== false && step
|
|
1770
|
+
}, children: [(popoverConfig.showSkip !== false && step?.canSkip !== false) && (jsxRuntimeExports.jsx("button", { onClick: (_e) => {
|
|
1381
1771
|
handleSkip();
|
|
1382
1772
|
}, style: {
|
|
1383
1773
|
background: 'none',
|
|
@@ -1417,7 +1807,7 @@ function TourPopover({ className }) {
|
|
|
1417
1807
|
fontWeight: '500',
|
|
1418
1808
|
opacity: canGoPrevious ? 1 : 0.5,
|
|
1419
1809
|
transition: 'all 0.2s ease',
|
|
1420
|
-
}, children: "Previous" })), jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1810
|
+
}, children: "Previous" })), !shouldClickToAdvance && (jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1421
1811
|
e.preventDefault();
|
|
1422
1812
|
e.stopPropagation();
|
|
1423
1813
|
handleNext();
|
|
@@ -1440,7 +1830,18 @@ function TourPopover({ className }) {
|
|
|
1440
1830
|
zIndex: 99999,
|
|
1441
1831
|
}, children: isLastStep
|
|
1442
1832
|
? (popoverConfig.finishLabel || 'Finish')
|
|
1443
|
-
: (popoverConfig.nextLabel || 'Next') })
|
|
1833
|
+
: (popoverConfig.nextLabel || 'Next') })), shouldClickToAdvance && (jsxRuntimeExports.jsx("div", { style: {
|
|
1834
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
1835
|
+
border: '1px solid rgba(59, 130, 246, 0.3)',
|
|
1836
|
+
color: theme.primaryColor || '#3b82f6',
|
|
1837
|
+
padding: '8px 16px',
|
|
1838
|
+
borderRadius: '8px',
|
|
1839
|
+
fontSize: '14px',
|
|
1840
|
+
fontWeight: '500',
|
|
1841
|
+
display: 'flex',
|
|
1842
|
+
alignItems: 'center',
|
|
1843
|
+
gap: '6px',
|
|
1844
|
+
}, children: "\uD83D\uDC46 Click the highlighted element to continue" }))] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
|
|
1444
1845
|
position: 'absolute',
|
|
1445
1846
|
width: '12px',
|
|
1446
1847
|
height: '12px',
|
|
@@ -1460,7 +1861,7 @@ function TourPopover({ className }) {
|
|
|
1460
1861
|
transform: 'rotate(45deg)',
|
|
1461
1862
|
...getArrowPosition(position.placement),
|
|
1462
1863
|
}, "data-tour-arrow": true }))] }));
|
|
1463
|
-
}
|
|
1864
|
+
});
|
|
1464
1865
|
function getArrowPosition(placement) {
|
|
1465
1866
|
switch (placement) {
|
|
1466
1867
|
case 'top':
|
|
@@ -1500,13 +1901,92 @@ function getArrowPosition(placement) {
|
|
|
1500
1901
|
}
|
|
1501
1902
|
}
|
|
1502
1903
|
|
|
1503
|
-
|
|
1904
|
+
class ErrorBoundary extends Component {
|
|
1905
|
+
constructor(props) {
|
|
1906
|
+
super(props);
|
|
1907
|
+
this.retryTimeoutId = null;
|
|
1908
|
+
this.retry = () => {
|
|
1909
|
+
this.setState({
|
|
1910
|
+
hasError: false,
|
|
1911
|
+
error: null,
|
|
1912
|
+
errorInfo: null,
|
|
1913
|
+
});
|
|
1914
|
+
};
|
|
1915
|
+
this.state = {
|
|
1916
|
+
hasError: false,
|
|
1917
|
+
error: null,
|
|
1918
|
+
errorInfo: null,
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
static getDerivedStateFromError(error) {
|
|
1922
|
+
return {
|
|
1923
|
+
hasError: true,
|
|
1924
|
+
error,
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
componentDidCatch(error, errorInfo) {
|
|
1928
|
+
this.setState({
|
|
1929
|
+
error,
|
|
1930
|
+
errorInfo,
|
|
1931
|
+
});
|
|
1932
|
+
if (this.props.onError) {
|
|
1933
|
+
this.props.onError(error, errorInfo);
|
|
1934
|
+
}
|
|
1935
|
+
console.error('Tour ErrorBoundary caught an error:', error, errorInfo);
|
|
1936
|
+
}
|
|
1937
|
+
componentWillUnmount() {
|
|
1938
|
+
if (this.retryTimeoutId) {
|
|
1939
|
+
clearTimeout(this.retryTimeoutId);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
render() {
|
|
1943
|
+
if (this.state.hasError && this.state.error && this.state.errorInfo) {
|
|
1944
|
+
if (this.props.fallback) {
|
|
1945
|
+
return this.props.fallback(this.state.error, this.state.errorInfo, this.retry);
|
|
1946
|
+
}
|
|
1947
|
+
return (jsxRuntimeExports.jsxs("div", { style: {
|
|
1948
|
+
padding: '20px',
|
|
1949
|
+
margin: '20px',
|
|
1950
|
+
border: '2px solid #ff6b6b',
|
|
1951
|
+
borderRadius: '8px',
|
|
1952
|
+
backgroundColor: '#fff5f5',
|
|
1953
|
+
color: '#c92a2a',
|
|
1954
|
+
fontFamily: 'system-ui, sans-serif'
|
|
1955
|
+
}, 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: {
|
|
1956
|
+
marginTop: '10px',
|
|
1957
|
+
padding: '10px',
|
|
1958
|
+
backgroundColor: '#f8f8f8',
|
|
1959
|
+
borderRadius: '4px',
|
|
1960
|
+
fontSize: '12px',
|
|
1961
|
+
overflow: 'auto'
|
|
1962
|
+
}, 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: {
|
|
1963
|
+
padding: '8px 16px',
|
|
1964
|
+
backgroundColor: '#228be6',
|
|
1965
|
+
color: 'white',
|
|
1966
|
+
border: 'none',
|
|
1967
|
+
borderRadius: '4px',
|
|
1968
|
+
cursor: 'pointer',
|
|
1969
|
+
fontSize: '14px'
|
|
1970
|
+
}, children: "Try Again" })] }));
|
|
1971
|
+
}
|
|
1972
|
+
return this.props.children;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
function withErrorBoundary(Component, errorBoundaryProps) {
|
|
1976
|
+
const WrappedComponent = (props) => (jsxRuntimeExports.jsx(ErrorBoundary, { ...errorBoundaryProps, children: jsxRuntimeExports.jsx(Component, { ...props }) }));
|
|
1977
|
+
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
|
|
1978
|
+
return WrappedComponent;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
const TourRunner = React.memo(function TourRunner({ className }) {
|
|
1504
1982
|
const { state } = useTour();
|
|
1505
1983
|
if (!state.isRunning) {
|
|
1506
1984
|
return null;
|
|
1507
1985
|
}
|
|
1508
|
-
return (jsxRuntimeExports.jsxs(
|
|
1509
|
-
|
|
1986
|
+
return (jsxRuntimeExports.jsxs(ErrorBoundary, { onError: (error, errorInfo) => {
|
|
1987
|
+
console.error('TourRunner error:', error, errorInfo);
|
|
1988
|
+
}, children: [jsxRuntimeExports.jsx(TourOverlay, { className: className }), jsxRuntimeExports.jsx(TourPopover, { className: className })] }));
|
|
1989
|
+
});
|
|
1510
1990
|
|
|
1511
1991
|
class TabIntegration {
|
|
1512
1992
|
constructor() {
|
|
@@ -1523,33 +2003,26 @@ class TabIntegration {
|
|
|
1523
2003
|
if (!targetElement) {
|
|
1524
2004
|
throw new Error(`Tab element not found: ${action.target}`);
|
|
1525
2005
|
}
|
|
1526
|
-
// Handle different tab implementations
|
|
1527
2006
|
const tabValue = typeof action.value === 'string' ? action.value : undefined;
|
|
1528
2007
|
await this.handleTabClick(targetElement, tabValue);
|
|
1529
2008
|
}
|
|
1530
2009
|
async handleTabClick(tabElement, tabValue) {
|
|
1531
|
-
// Check for common tab patterns
|
|
1532
|
-
// 1. Radix UI Tabs
|
|
1533
2010
|
if (this.isRadixTab(tabElement)) {
|
|
1534
2011
|
await this.handleRadixTab(tabElement);
|
|
1535
2012
|
return;
|
|
1536
2013
|
}
|
|
1537
|
-
// 2. Material UI Tabs
|
|
1538
2014
|
if (this.isMaterialUITab(tabElement)) {
|
|
1539
2015
|
await this.handleMaterialUITab(tabElement);
|
|
1540
2016
|
return;
|
|
1541
2017
|
}
|
|
1542
|
-
// 3. React Router tabs (links)
|
|
1543
2018
|
if (this.isRouterTab(tabElement)) {
|
|
1544
2019
|
await this.handleRouterTab(tabElement);
|
|
1545
2020
|
return;
|
|
1546
2021
|
}
|
|
1547
|
-
// 4. Custom tabs with data attributes
|
|
1548
2022
|
if (this.isCustomTab(tabElement)) {
|
|
1549
2023
|
await this.handleCustomTab(tabElement, tabValue);
|
|
1550
2024
|
return;
|
|
1551
2025
|
}
|
|
1552
|
-
// 5. Generic button/clickable tab
|
|
1553
2026
|
await this.handleGenericTab(tabElement);
|
|
1554
2027
|
}
|
|
1555
2028
|
isRadixTab(element) {
|
|
@@ -2093,5 +2566,5 @@ class NavigationIntegration {
|
|
|
2093
2566
|
}
|
|
2094
2567
|
}
|
|
2095
2568
|
|
|
2096
|
-
export { NavigationIntegration, TabIntegration, TourActions, TourEngine, TourOverlay, TourPopover, TourProvider, TourRunner, TourStorage, WizardIntegration, calculatePopoverPosition, getElementPosition, getViewportCenter, isElementInViewport, scrollToElement, useTour, useTourEngine, useTourHighlight };
|
|
2569
|
+
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
2570
|
//# sourceMappingURL=index.esm.js.map
|