@aladinbs/react-guided-tour 1.0.3 → 1.2.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 +200 -14
- package/dist/components/TourOverlay.d.ts.map +1 -1
- package/dist/components/TourPopover.d.ts.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.esm.js +206 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +206 -12
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1416,8 +1416,21 @@ function useTourHighlight(step) {
|
|
|
1416
1416
|
}
|
|
1417
1417
|
|
|
1418
1418
|
const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
1419
|
-
const { state, theme, stop } = useTour();
|
|
1419
|
+
const { state, theme, stop, config, next } = useTour();
|
|
1420
1420
|
const { targetElement, highlightStyle, isVisible } = useTourHighlight(state.currentStep);
|
|
1421
|
+
const [scrollTrigger, setScrollTrigger] = React.useState(0);
|
|
1422
|
+
// Determine if interactions should be blocked
|
|
1423
|
+
const shouldBlockInteractions = React.useMemo(() => {
|
|
1424
|
+
const stepBlocking = state.currentStep?.blockInteractions;
|
|
1425
|
+
const globalBlocking = config.blockInteractions;
|
|
1426
|
+
return stepBlocking !== undefined ? stepBlocking : globalBlocking;
|
|
1427
|
+
}, [state.currentStep?.blockInteractions, config.blockInteractions]);
|
|
1428
|
+
// Determine if click-to-advance is enabled
|
|
1429
|
+
const shouldClickToAdvance = React.useMemo(() => {
|
|
1430
|
+
const stepClickToAdvance = state.currentStep?.clickToAdvance;
|
|
1431
|
+
const globalClickToAdvance = config.clickToAdvance;
|
|
1432
|
+
return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
|
|
1433
|
+
}, [state.currentStep?.clickToAdvance, config.clickToAdvance]);
|
|
1421
1434
|
const overlayStyle = React.useMemo(() => ({
|
|
1422
1435
|
position: 'fixed',
|
|
1423
1436
|
top: 0,
|
|
@@ -1429,15 +1442,148 @@ const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
|
1429
1442
|
zIndex: (theme.zIndex || 9999) - 1,
|
|
1430
1443
|
pointerEvents: 'auto',
|
|
1431
1444
|
}), [theme.overlay?.backgroundColor, theme.overlay?.opacity, theme.zIndex]);
|
|
1432
|
-
|
|
1433
|
-
|
|
1445
|
+
// Create cutout style for the highlighted element
|
|
1446
|
+
const cutoutOverlayStyle = React.useMemo(() => {
|
|
1447
|
+
if ((!shouldBlockInteractions && !shouldClickToAdvance) || !targetElement || !isVisible) {
|
|
1448
|
+
return overlayStyle;
|
|
1449
|
+
}
|
|
1450
|
+
// Use getBoundingClientRect for viewport-relative coordinates (perfect for fixed overlay)
|
|
1451
|
+
const rect = targetElement.getBoundingClientRect();
|
|
1452
|
+
const padding = state.currentStep?.highlight?.padding || 8;
|
|
1453
|
+
// Ensure coordinates are within viewport bounds
|
|
1454
|
+
const left = Math.max(0, rect.left - padding);
|
|
1455
|
+
const top = Math.max(0, rect.top - padding);
|
|
1456
|
+
const right = Math.min(window.innerWidth, rect.right + padding);
|
|
1457
|
+
const bottom = Math.min(window.innerHeight, rect.bottom + padding);
|
|
1458
|
+
return {
|
|
1459
|
+
...overlayStyle,
|
|
1460
|
+
clipPath: `polygon(
|
|
1461
|
+
0% 0%,
|
|
1462
|
+
0% 100%,
|
|
1463
|
+
${left}px 100%,
|
|
1464
|
+
${left}px ${top}px,
|
|
1465
|
+
${right}px ${top}px,
|
|
1466
|
+
${right}px ${bottom}px,
|
|
1467
|
+
${left}px ${bottom}px,
|
|
1468
|
+
${left}px 100%,
|
|
1469
|
+
100% 100%,
|
|
1470
|
+
100% 0%
|
|
1471
|
+
)`,
|
|
1472
|
+
};
|
|
1473
|
+
}, [overlayStyle, shouldBlockInteractions, shouldClickToAdvance, targetElement, isVisible, state.currentStep?.highlight?.padding, scrollTrigger]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
1474
|
+
// Force re-render of cutout overlay on scroll to maintain proper positioning
|
|
1475
|
+
React.useEffect(() => {
|
|
1476
|
+
if (!shouldBlockInteractions && !shouldClickToAdvance) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
const handleScroll = () => {
|
|
1480
|
+
// Force recalculation by updating scroll trigger
|
|
1481
|
+
setScrollTrigger(prev => prev + 1);
|
|
1482
|
+
};
|
|
1483
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1484
|
+
window.addEventListener('resize', handleScroll, { passive: true });
|
|
1485
|
+
return () => {
|
|
1486
|
+
window.removeEventListener('scroll', handleScroll);
|
|
1487
|
+
window.removeEventListener('resize', handleScroll);
|
|
1488
|
+
};
|
|
1489
|
+
}, [shouldBlockInteractions, shouldClickToAdvance]);
|
|
1490
|
+
const handleOverlayClick = React.useCallback((e) => {
|
|
1491
|
+
// If interactions are blocked, prevent the click from propagating
|
|
1492
|
+
if (shouldBlockInteractions) {
|
|
1493
|
+
e.preventDefault();
|
|
1494
|
+
e.stopPropagation();
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
// Allow clicking outside to close tour if not blocking interactions
|
|
1498
|
+
if (config.allowClickOutside !== false && state.currentStep?.canSkip !== false) {
|
|
1434
1499
|
stop();
|
|
1435
1500
|
}
|
|
1436
|
-
}, [state.currentStep?.canSkip, stop]);
|
|
1501
|
+
}, [shouldBlockInteractions, config.allowClickOutside, state.currentStep?.canSkip, stop]);
|
|
1502
|
+
// Handle click-to-advance functionality
|
|
1503
|
+
React.useEffect(() => {
|
|
1504
|
+
if (!shouldClickToAdvance || !targetElement || !state.isRunning) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
// Ensure target element is above the overlay when click-to-advance is enabled
|
|
1508
|
+
const originalZIndex = targetElement.style.zIndex;
|
|
1509
|
+
const originalPosition = targetElement.style.position;
|
|
1510
|
+
targetElement.style.position = targetElement.style.position || 'relative';
|
|
1511
|
+
targetElement.style.zIndex = String((theme.zIndex || 9999) + 1);
|
|
1512
|
+
const handleTargetClick = (_e) => {
|
|
1513
|
+
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
|
+
React.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]);
|
|
1437
1571
|
if (!state.isRunning || !state.currentStep) {
|
|
1438
1572
|
return null;
|
|
1439
1573
|
}
|
|
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("
|
|
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: `
|
|
1441
1587
|
@keyframes tour-highlight-pulse {
|
|
1442
1588
|
0%, 100% {
|
|
1443
1589
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.3);
|
|
@@ -1465,24 +1611,53 @@ const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
|
1465
1611
|
[data-tour-highlight] {
|
|
1466
1612
|
animation: tour-fade-in 0.3s ease-out;
|
|
1467
1613
|
}
|
|
1614
|
+
|
|
1615
|
+
[data-tour-blocking-indicator] {
|
|
1616
|
+
animation: tour-fade-in 0.3s ease-out;
|
|
1617
|
+
}
|
|
1468
1618
|
` })] }));
|
|
1469
1619
|
});
|
|
1470
1620
|
|
|
1471
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}
|
|
1472
1622
|
|
|
1473
1623
|
const TourPopover = React.memo(function TourPopover({ className }) {
|
|
1474
|
-
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();
|
|
1475
1625
|
const { targetElement } = useTourHighlight(state.currentStep);
|
|
1476
1626
|
const popoverRef = React.useRef(null);
|
|
1477
1627
|
const [position, setPosition] = React.useState({ top: 0, left: 0, placement: 'top' });
|
|
1478
1628
|
const updatePosition = React.useCallback(() => {
|
|
1479
|
-
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)
|
|
1480
1649
|
return;
|
|
1481
1650
|
const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
|
|
1482
1651
|
setPosition(newPosition);
|
|
1483
1652
|
}, [targetElement, state.currentStep]);
|
|
1484
1653
|
const step = state.currentStep;
|
|
1485
1654
|
const popoverConfig = React.useMemo(() => step?.popover || {}, [step?.popover]);
|
|
1655
|
+
// Determine if click-to-advance is enabled for this step
|
|
1656
|
+
const shouldClickToAdvance = React.useMemo(() => {
|
|
1657
|
+
const stepClickToAdvance = step?.clickToAdvance;
|
|
1658
|
+
const globalClickToAdvance = config.clickToAdvance;
|
|
1659
|
+
return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
|
|
1660
|
+
}, [step?.clickToAdvance, config.clickToAdvance]);
|
|
1486
1661
|
const popoverStyle = React.useMemo(() => ({
|
|
1487
1662
|
position: 'absolute',
|
|
1488
1663
|
top: position.top,
|
|
@@ -1515,10 +1690,14 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1515
1690
|
}
|
|
1516
1691
|
}, [canGoNext, isLastStep, next]);
|
|
1517
1692
|
const handlePrevious = React.useCallback(async () => {
|
|
1693
|
+
if (step?.previousButton?.handler) {
|
|
1694
|
+
await step.previousButton.handler();
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1518
1697
|
if (canGoPrevious) {
|
|
1519
1698
|
await previous();
|
|
1520
1699
|
}
|
|
1521
|
-
}, [canGoPrevious, previous]);
|
|
1700
|
+
}, [canGoPrevious, previous, step?.previousButton]);
|
|
1522
1701
|
const handleSkip = React.useCallback(async () => {
|
|
1523
1702
|
await skip();
|
|
1524
1703
|
}, [skip]);
|
|
@@ -1526,12 +1705,16 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1526
1705
|
await stop();
|
|
1527
1706
|
}, [stop]);
|
|
1528
1707
|
React.useEffect(() => {
|
|
1529
|
-
|
|
1708
|
+
// Small delay to ensure popover is rendered and has dimensions
|
|
1709
|
+
const timeoutId = setTimeout(() => {
|
|
1710
|
+
updatePosition();
|
|
1711
|
+
}, 10);
|
|
1530
1712
|
const handleResize = () => updatePosition();
|
|
1531
1713
|
const handleScroll = () => updatePosition();
|
|
1532
1714
|
window.addEventListener('resize', handleResize);
|
|
1533
1715
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1534
1716
|
return () => {
|
|
1717
|
+
clearTimeout(timeoutId);
|
|
1535
1718
|
window.removeEventListener('resize', handleResize);
|
|
1536
1719
|
window.removeEventListener('scroll', handleScroll);
|
|
1537
1720
|
};
|
|
@@ -1606,7 +1789,7 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1606
1789
|
pointerEvents: 'auto',
|
|
1607
1790
|
position: 'relative',
|
|
1608
1791
|
zIndex: 99999,
|
|
1609
|
-
}, children: popoverConfig.skipLabel || 'Skip Tour' })), jsxRuntimeExports.jsxs("div", { style: { display: 'flex', gap: '8px', marginLeft: 'auto' }, children: [!isFirstStep && (jsxRuntimeExports.jsx("button", { onClick: handlePrevious, disabled: !canGoPrevious, style: {
|
|
1792
|
+
}, children: popoverConfig.skipLabel || 'Skip Tour' })), jsxRuntimeExports.jsxs("div", { style: { display: 'flex', gap: '8px', marginLeft: 'auto' }, children: [!isFirstStep && (step?.previousButton?.show !== false) && (jsxRuntimeExports.jsx("button", { onClick: handlePrevious, disabled: !canGoPrevious, style: {
|
|
1610
1793
|
backgroundColor: 'transparent',
|
|
1611
1794
|
borderTopWidth: '1px',
|
|
1612
1795
|
borderRightWidth: '1px',
|
|
@@ -1628,7 +1811,7 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1628
1811
|
fontWeight: '500',
|
|
1629
1812
|
opacity: canGoPrevious ? 1 : 0.5,
|
|
1630
1813
|
transition: 'all 0.2s ease',
|
|
1631
|
-
}, children:
|
|
1814
|
+
}, children: step?.previousButton?.label || 'Previous' })), !shouldClickToAdvance && (jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1632
1815
|
e.preventDefault();
|
|
1633
1816
|
e.stopPropagation();
|
|
1634
1817
|
handleNext();
|
|
@@ -1651,7 +1834,18 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1651
1834
|
zIndex: 99999,
|
|
1652
1835
|
}, children: isLastStep
|
|
1653
1836
|
? (popoverConfig.finishLabel || 'Finish')
|
|
1654
|
-
: (popoverConfig.nextLabel || 'Next') })
|
|
1837
|
+
: (popoverConfig.nextLabel || 'Next') })), shouldClickToAdvance && (jsxRuntimeExports.jsx("div", { style: {
|
|
1838
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
1839
|
+
border: '1px solid rgba(59, 130, 246, 0.3)',
|
|
1840
|
+
color: theme.primaryColor || '#3b82f6',
|
|
1841
|
+
padding: '8px 16px',
|
|
1842
|
+
borderRadius: '8px',
|
|
1843
|
+
fontSize: '14px',
|
|
1844
|
+
fontWeight: '500',
|
|
1845
|
+
display: 'flex',
|
|
1846
|
+
alignItems: 'center',
|
|
1847
|
+
gap: '6px',
|
|
1848
|
+
}, children: "\uD83D\uDC46 Click the highlighted element to continue" }))] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
|
|
1655
1849
|
position: 'absolute',
|
|
1656
1850
|
width: '12px',
|
|
1657
1851
|
height: '12px',
|