@aladinbs/react-guided-tour 1.0.3 → 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 +92 -0
- package/dist/components/TourOverlay.d.ts.map +1 -1
- package/dist/components/TourPopover.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +202 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +202 -10
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ A modern, flexible React TypeScript tour guide library with advanced highlightin
|
|
|
12
12
|
- 💾 **State Persistence** - localStorage integration to remember tour completion and progress
|
|
13
13
|
- ⚡ **Performance Optimized** - Framework-agnostic core with efficient React integration
|
|
14
14
|
- 🎪 **Action System** - Automated interactions (clicks, navigation, tab switching)
|
|
15
|
+
- 🔒 **Interaction Blocking** - Prevent user interactions outside the tour target for focused guidance
|
|
16
|
+
- 👆 **Click-to-Advance** - Allow users to click target elements directly to advance tour steps
|
|
15
17
|
- 🔄 **Event System** - Rich event hooks for tour lifecycle management
|
|
16
18
|
- 🎯 **TypeScript First** - Full type safety with comprehensive type definitions
|
|
17
19
|
|
|
@@ -141,6 +143,8 @@ interface TourConfig {
|
|
|
141
143
|
onStepChange?: (step: TourStep, index: number) => void;
|
|
142
144
|
allowKeyboardNavigation?: boolean; // Enable keyboard controls
|
|
143
145
|
allowClickOutside?: boolean; // Allow clicking outside to close
|
|
146
|
+
blockInteractions?: boolean; // Block interactions outside tour target
|
|
147
|
+
clickToAdvance?: boolean; // Enable click-to-advance globally
|
|
144
148
|
showProgress?: boolean; // Show step progress indicator
|
|
145
149
|
storage?: {
|
|
146
150
|
key?: string; // localStorage key (default: 'tour-{id}')
|
|
@@ -168,6 +172,8 @@ interface TourStep {
|
|
|
168
172
|
canSkip?: boolean; // Allow skipping this step
|
|
169
173
|
waitForElement?: boolean; // Wait for target element to appear
|
|
170
174
|
waitTimeout?: number; // Timeout for element waiting (ms)
|
|
175
|
+
blockInteractions?: boolean; // Override global interaction blocking for this step
|
|
176
|
+
clickToAdvance?: boolean; // Override global click-to-advance for this step
|
|
171
177
|
}
|
|
172
178
|
```
|
|
173
179
|
|
|
@@ -279,6 +285,92 @@ Navigate through multi-step wizards:
|
|
|
279
285
|
}
|
|
280
286
|
```
|
|
281
287
|
|
|
288
|
+
### Interaction Blocking
|
|
289
|
+
|
|
290
|
+
Control user interactions during the tour to create focused guidance experiences:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
const tourConfig: TourConfig = {
|
|
294
|
+
id: 'focused-tour',
|
|
295
|
+
blockInteractions: true, // Block interactions globally
|
|
296
|
+
steps: [
|
|
297
|
+
{
|
|
298
|
+
id: 'step1',
|
|
299
|
+
title: 'Focused Step',
|
|
300
|
+
content: 'Users can only interact with the highlighted element.',
|
|
301
|
+
target: '#important-button',
|
|
302
|
+
blockInteractions: true, // Enforce blocking for this step
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: 'step2',
|
|
306
|
+
title: 'Free Interaction',
|
|
307
|
+
content: 'Users can click anywhere during this step.',
|
|
308
|
+
target: '#another-element',
|
|
309
|
+
blockInteractions: false, // Override global setting for this step
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Features:**
|
|
316
|
+
- **Global Control**: Set `blockInteractions: true` in tour config to block interactions for all steps
|
|
317
|
+
- **Per-Step Override**: Use `blockInteractions` in individual steps to override the global setting
|
|
318
|
+
- **Visual Feedback**: Shows a "🔒 Interactions blocked" indicator when active
|
|
319
|
+
- **Smart Targeting**: Only the highlighted element and tour UI remain interactive
|
|
320
|
+
- **Keyboard Blocking**: Prevents keyboard interactions except tour navigation keys
|
|
321
|
+
|
|
322
|
+
**When to use:**
|
|
323
|
+
- Complex interfaces where users might get distracted
|
|
324
|
+
- Critical onboarding flows that must be completed in order
|
|
325
|
+
- Preventing accidental clicks that could break the tour flow
|
|
326
|
+
- Ensuring users focus on specific elements
|
|
327
|
+
|
|
328
|
+
### Click-to-Advance
|
|
329
|
+
|
|
330
|
+
Enable users to interact directly with tour target elements to advance to the next step:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
const tourConfig: TourConfig = {
|
|
334
|
+
id: "interactive-tour",
|
|
335
|
+
clickToAdvance: true, // Enable globally
|
|
336
|
+
steps: [
|
|
337
|
+
{
|
|
338
|
+
id: "button-interaction",
|
|
339
|
+
title: "Try the Button",
|
|
340
|
+
content: "Click the button below to continue the tour!",
|
|
341
|
+
target: "#my-button",
|
|
342
|
+
clickToAdvance: true, // Enable for this step
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "form-interaction",
|
|
346
|
+
title: "Fill the Form",
|
|
347
|
+
content: "Complete this form field to proceed.",
|
|
348
|
+
target: "#email-input",
|
|
349
|
+
clickToAdvance: false, // Disable for this step (use Next button)
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Features:**
|
|
356
|
+
- **Global Control**: Set `clickToAdvance: true` in tour config to enable for all steps
|
|
357
|
+
- **Per-Step Override**: Use `clickToAdvance` in individual steps to override the global setting
|
|
358
|
+
- **Smart UI**: Next button is automatically hidden when click-to-advance is active
|
|
359
|
+
- **Visual Guidance**: Shows "👆 Click the highlighted element to continue" instruction
|
|
360
|
+
- **Event Handling**: Automatically captures clicks on target elements and advances tour
|
|
361
|
+
|
|
362
|
+
**When to use:**
|
|
363
|
+
- Interactive tutorials where users need to practice using actual UI elements
|
|
364
|
+
- Onboarding flows for buttons, forms, and interactive components
|
|
365
|
+
- Training scenarios where clicking the real element is part of the learning
|
|
366
|
+
- Reducing cognitive load by eliminating the need to find the Next button
|
|
367
|
+
|
|
368
|
+
**Best practices:**
|
|
369
|
+
- Use for clickable elements like buttons, links, and form controls
|
|
370
|
+
- Combine with `blockInteractions: true` to ensure users click the right element
|
|
371
|
+
- Provide clear instructions in the step content about what to click
|
|
372
|
+
- Consider accessibility - ensure target elements are keyboard accessible
|
|
373
|
+
|
|
282
374
|
### Custom Integrations
|
|
283
375
|
|
|
284
376
|
Extend the tour system with custom integrations:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TourOverlay.d.ts","sourceRoot":"","sources":["../../src/components/TourOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"TourOverlay.d.ts","sourceRoot":"","sources":["../../src/components/TourOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAC;AAIzE,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,8CAwQtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TourPopover.d.ts","sourceRoot":"","sources":["../../src/components/TourPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAOjF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"TourPopover.d.ts","sourceRoot":"","sources":["../../src/components/TourPopover.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAOjF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,8CAsWtB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ interface TourStep {
|
|
|
15
15
|
canSkip?: boolean;
|
|
16
16
|
waitForElement?: boolean;
|
|
17
17
|
waitTimeout?: number;
|
|
18
|
+
blockInteractions?: boolean;
|
|
19
|
+
clickToAdvance?: boolean;
|
|
18
20
|
}
|
|
19
21
|
interface TourAction {
|
|
20
22
|
type: 'click' | 'navigate' | 'highlight' | 'tab-switch' | 'wizard-step' | 'custom';
|
|
@@ -52,6 +54,8 @@ interface TourConfig {
|
|
|
52
54
|
onStepChange?: (step: TourStep, index: number) => void;
|
|
53
55
|
allowKeyboardNavigation?: boolean;
|
|
54
56
|
allowClickOutside?: boolean;
|
|
57
|
+
blockInteractions?: boolean;
|
|
58
|
+
clickToAdvance?: boolean;
|
|
55
59
|
showProgress?: boolean;
|
|
56
60
|
storage?: {
|
|
57
61
|
key?: string;
|
package/dist/index.esm.js
CHANGED
|
@@ -1414,8 +1414,21 @@ function useTourHighlight(step) {
|
|
|
1414
1414
|
}
|
|
1415
1415
|
|
|
1416
1416
|
const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
1417
|
-
const { state, theme, stop } = useTour();
|
|
1417
|
+
const { state, theme, stop, config, next } = useTour();
|
|
1418
1418
|
const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
|
|
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]);
|
|
1419
1432
|
const overlayStyle = useMemo(() => ({
|
|
1420
1433
|
position: 'fixed',
|
|
1421
1434
|
top: 0,
|
|
@@ -1427,15 +1440,150 @@ const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
|
1427
1440
|
zIndex: (theme.zIndex || 9999) - 1,
|
|
1428
1441
|
pointerEvents: 'auto',
|
|
1429
1442
|
}), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
|
|
1430
|
-
|
|
1431
|
-
|
|
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) {
|
|
1432
1497
|
stop();
|
|
1433
1498
|
}
|
|
1434
|
-
}, [state.currentStep?.canSkip, stop]);
|
|
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]);
|
|
1435
1571
|
if (!state.isRunning || !state.currentStep) {
|
|
1436
1572
|
return null;
|
|
1437
1573
|
}
|
|
1438
|
-
return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: overlayStyle, onClick: handleOverlayClick, className: className, "data-tour-overlay": true }), isVisible && targetElement && (jsxRuntimeExports.jsx("div", { style: highlightStyle, "data-tour-highlight": true })), jsxRuntimeExports.jsx("
|
|
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: `
|
|
1439
1587
|
@keyframes tour-highlight-pulse {
|
|
1440
1588
|
0%, 100% {
|
|
1441
1589
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.3);
|
|
@@ -1463,24 +1611,53 @@ const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
|
1463
1611
|
[data-tour-highlight] {
|
|
1464
1612
|
animation: tour-fade-in 0.3s ease-out;
|
|
1465
1613
|
}
|
|
1614
|
+
|
|
1615
|
+
[data-tour-blocking-indicator] {
|
|
1616
|
+
animation: tour-fade-in 0.3s ease-out;
|
|
1617
|
+
}
|
|
1466
1618
|
` })] }));
|
|
1467
1619
|
});
|
|
1468
1620
|
|
|
1469
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}
|
|
1470
1622
|
|
|
1471
1623
|
const TourPopover = React.memo(function TourPopover({ className }) {
|
|
1472
|
-
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
|
|
1624
|
+
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious, config } = useTour();
|
|
1473
1625
|
const { targetElement } = useTourHighlight(state.currentStep);
|
|
1474
1626
|
const popoverRef = useRef(null);
|
|
1475
1627
|
const [position, setPosition] = useState({ top: 0, left: 0, placement: 'top' });
|
|
1476
1628
|
const updatePosition = useCallback(() => {
|
|
1477
|
-
if (!popoverRef.current || !
|
|
1629
|
+
if (!popoverRef.current || !state.currentStep)
|
|
1630
|
+
return;
|
|
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)
|
|
1478
1649
|
return;
|
|
1479
1650
|
const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
|
|
1480
1651
|
setPosition(newPosition);
|
|
1481
1652
|
}, [targetElement, state.currentStep]);
|
|
1482
1653
|
const step = state.currentStep;
|
|
1483
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]);
|
|
1484
1661
|
const popoverStyle = useMemo(() => ({
|
|
1485
1662
|
position: 'absolute',
|
|
1486
1663
|
top: position.top,
|
|
@@ -1524,12 +1701,16 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1524
1701
|
await stop();
|
|
1525
1702
|
}, [stop]);
|
|
1526
1703
|
useEffect(() => {
|
|
1527
|
-
|
|
1704
|
+
// Small delay to ensure popover is rendered and has dimensions
|
|
1705
|
+
const timeoutId = setTimeout(() => {
|
|
1706
|
+
updatePosition();
|
|
1707
|
+
}, 10);
|
|
1528
1708
|
const handleResize = () => updatePosition();
|
|
1529
1709
|
const handleScroll = () => updatePosition();
|
|
1530
1710
|
window.addEventListener('resize', handleResize);
|
|
1531
1711
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1532
1712
|
return () => {
|
|
1713
|
+
clearTimeout(timeoutId);
|
|
1533
1714
|
window.removeEventListener('resize', handleResize);
|
|
1534
1715
|
window.removeEventListener('scroll', handleScroll);
|
|
1535
1716
|
};
|
|
@@ -1626,7 +1807,7 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1626
1807
|
fontWeight: '500',
|
|
1627
1808
|
opacity: canGoPrevious ? 1 : 0.5,
|
|
1628
1809
|
transition: 'all 0.2s ease',
|
|
1629
|
-
}, children: "Previous" })), jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1810
|
+
}, children: "Previous" })), !shouldClickToAdvance && (jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1630
1811
|
e.preventDefault();
|
|
1631
1812
|
e.stopPropagation();
|
|
1632
1813
|
handleNext();
|
|
@@ -1649,7 +1830,18 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1649
1830
|
zIndex: 99999,
|
|
1650
1831
|
}, children: isLastStep
|
|
1651
1832
|
? (popoverConfig.finishLabel || 'Finish')
|
|
1652
|
-
: (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: {
|
|
1653
1845
|
position: 'absolute',
|
|
1654
1846
|
width: '12px',
|
|
1655
1847
|
height: '12px',
|