@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/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,150 @@ 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
|
+
// Don't prevent default or stop propagation - let the element's normal behavior work
|
|
1514
|
+
// Just advance the tour after a small delay to allow the click to be processed
|
|
1515
|
+
setTimeout(() => {
|
|
1516
|
+
next();
|
|
1517
|
+
}, 100);
|
|
1518
|
+
};
|
|
1519
|
+
targetElement.addEventListener('click', handleTargetClick, true);
|
|
1520
|
+
return () => {
|
|
1521
|
+
targetElement.removeEventListener('click', handleTargetClick, true);
|
|
1522
|
+
// Restore original styles
|
|
1523
|
+
targetElement.style.zIndex = originalZIndex;
|
|
1524
|
+
targetElement.style.position = originalPosition;
|
|
1525
|
+
};
|
|
1526
|
+
}, [shouldClickToAdvance, targetElement, state.isRunning, next, theme.zIndex]);
|
|
1527
|
+
// Block interactions on the entire page when blocking is enabled
|
|
1528
|
+
React.useEffect(() => {
|
|
1529
|
+
if (!shouldBlockInteractions || !state.isRunning) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const handleGlobalClick = (e) => {
|
|
1533
|
+
const target = e.target;
|
|
1534
|
+
// Allow clicks on the tour target element and its children
|
|
1535
|
+
if (targetElement && (targetElement.contains(target) || targetElement === target)) {
|
|
1536
|
+
// If click-to-advance is enabled, let the target click handler deal with it
|
|
1537
|
+
if (shouldClickToAdvance) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
// Allow clicks on tour UI elements (popover, buttons, etc.)
|
|
1543
|
+
if (target.closest('[data-tour-popover]') ||
|
|
1544
|
+
target.closest('[data-tour-highlight]') ||
|
|
1545
|
+
target.closest('[data-tour-overlay]')) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
// Block all other clicks
|
|
1549
|
+
e.preventDefault();
|
|
1550
|
+
e.stopPropagation();
|
|
1551
|
+
};
|
|
1552
|
+
const handleGlobalKeydown = (e) => {
|
|
1553
|
+
// Allow tour navigation keys
|
|
1554
|
+
if (['Escape', 'ArrowLeft', 'ArrowRight', 'Enter', 'Space'].includes(e.key)) {
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
// Block other keyboard interactions
|
|
1558
|
+
e.preventDefault();
|
|
1559
|
+
e.stopPropagation();
|
|
1560
|
+
};
|
|
1561
|
+
// Add event listeners to capture phase to block interactions early
|
|
1562
|
+
document.addEventListener('click', handleGlobalClick, true);
|
|
1563
|
+
document.addEventListener('keydown', handleGlobalKeydown, true);
|
|
1564
|
+
document.addEventListener('mousedown', handleGlobalClick, true);
|
|
1565
|
+
document.addEventListener('touchstart', handleGlobalClick, true);
|
|
1566
|
+
return () => {
|
|
1567
|
+
document.removeEventListener('click', handleGlobalClick, true);
|
|
1568
|
+
document.removeEventListener('keydown', handleGlobalKeydown, true);
|
|
1569
|
+
document.removeEventListener('mousedown', handleGlobalClick, true);
|
|
1570
|
+
document.removeEventListener('touchstart', handleGlobalClick, true);
|
|
1571
|
+
};
|
|
1572
|
+
}, [shouldBlockInteractions, state.isRunning, targetElement, shouldClickToAdvance]);
|
|
1437
1573
|
if (!state.isRunning || !state.currentStep) {
|
|
1438
1574
|
return null;
|
|
1439
1575
|
}
|
|
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("
|
|
1576
|
+
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: {
|
|
1577
|
+
position: 'fixed',
|
|
1578
|
+
top: '20px',
|
|
1579
|
+
right: '20px',
|
|
1580
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
1581
|
+
color: 'white',
|
|
1582
|
+
padding: '8px 12px',
|
|
1583
|
+
borderRadius: '4px',
|
|
1584
|
+
fontSize: '12px',
|
|
1585
|
+
zIndex: (theme.zIndex || 9999) + 1,
|
|
1586
|
+
pointerEvents: 'none',
|
|
1587
|
+
animation: 'tour-fade-in 0.3s ease-out',
|
|
1588
|
+
}, "data-tour-blocking-indicator": true, children: "\uD83D\uDD12 Interactions blocked" })), jsxRuntimeExports.jsx("style", { children: `
|
|
1441
1589
|
@keyframes tour-highlight-pulse {
|
|
1442
1590
|
0%, 100% {
|
|
1443
1591
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.2), 0 0 20px rgba(59, 130, 246, 0.3);
|
|
@@ -1465,24 +1613,53 @@ const TourOverlay = React.memo(function TourOverlay({ className }) {
|
|
|
1465
1613
|
[data-tour-highlight] {
|
|
1466
1614
|
animation: tour-fade-in 0.3s ease-out;
|
|
1467
1615
|
}
|
|
1616
|
+
|
|
1617
|
+
[data-tour-blocking-indicator] {
|
|
1618
|
+
animation: tour-fade-in 0.3s ease-out;
|
|
1619
|
+
}
|
|
1468
1620
|
` })] }));
|
|
1469
1621
|
});
|
|
1470
1622
|
|
|
1471
1623
|
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
1624
|
|
|
1473
1625
|
const TourPopover = React.memo(function TourPopover({ className }) {
|
|
1474
|
-
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious } = useTour();
|
|
1626
|
+
const { state, theme, next, previous, skip, stop, isFirstStep, isLastStep, canGoNext, canGoPrevious, config } = useTour();
|
|
1475
1627
|
const { targetElement } = useTourHighlight(state.currentStep);
|
|
1476
1628
|
const popoverRef = React.useRef(null);
|
|
1477
1629
|
const [position, setPosition] = React.useState({ top: 0, left: 0, placement: 'top' });
|
|
1478
1630
|
const updatePosition = React.useCallback(() => {
|
|
1479
|
-
if (!popoverRef.current || !
|
|
1631
|
+
if (!popoverRef.current || !state.currentStep)
|
|
1632
|
+
return;
|
|
1633
|
+
// For center placement without target element, calculate center position directly
|
|
1634
|
+
if (state.currentStep.placement === 'center' && !targetElement) {
|
|
1635
|
+
const popoverRect = popoverRef.current.getBoundingClientRect();
|
|
1636
|
+
const viewport = {
|
|
1637
|
+
width: window.innerWidth,
|
|
1638
|
+
height: window.innerHeight,
|
|
1639
|
+
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
|
|
1640
|
+
scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
|
|
1641
|
+
};
|
|
1642
|
+
setPosition({
|
|
1643
|
+
top: viewport.scrollTop + (viewport.height - popoverRect.height) / 2,
|
|
1644
|
+
left: viewport.scrollLeft + (viewport.width - popoverRect.width) / 2,
|
|
1645
|
+
placement: 'center',
|
|
1646
|
+
});
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
// For other placements, require target element
|
|
1650
|
+
if (!targetElement)
|
|
1480
1651
|
return;
|
|
1481
1652
|
const newPosition = calculatePopoverPosition(targetElement, popoverRef.current, state.currentStep.placement || 'top');
|
|
1482
1653
|
setPosition(newPosition);
|
|
1483
1654
|
}, [targetElement, state.currentStep]);
|
|
1484
1655
|
const step = state.currentStep;
|
|
1485
1656
|
const popoverConfig = React.useMemo(() => step?.popover || {}, [step?.popover]);
|
|
1657
|
+
// Determine if click-to-advance is enabled for this step
|
|
1658
|
+
const shouldClickToAdvance = React.useMemo(() => {
|
|
1659
|
+
const stepClickToAdvance = step?.clickToAdvance;
|
|
1660
|
+
const globalClickToAdvance = config.clickToAdvance;
|
|
1661
|
+
return stepClickToAdvance !== undefined ? stepClickToAdvance : globalClickToAdvance;
|
|
1662
|
+
}, [step?.clickToAdvance, config.clickToAdvance]);
|
|
1486
1663
|
const popoverStyle = React.useMemo(() => ({
|
|
1487
1664
|
position: 'absolute',
|
|
1488
1665
|
top: position.top,
|
|
@@ -1526,12 +1703,16 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1526
1703
|
await stop();
|
|
1527
1704
|
}, [stop]);
|
|
1528
1705
|
React.useEffect(() => {
|
|
1529
|
-
|
|
1706
|
+
// Small delay to ensure popover is rendered and has dimensions
|
|
1707
|
+
const timeoutId = setTimeout(() => {
|
|
1708
|
+
updatePosition();
|
|
1709
|
+
}, 10);
|
|
1530
1710
|
const handleResize = () => updatePosition();
|
|
1531
1711
|
const handleScroll = () => updatePosition();
|
|
1532
1712
|
window.addEventListener('resize', handleResize);
|
|
1533
1713
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
1534
1714
|
return () => {
|
|
1715
|
+
clearTimeout(timeoutId);
|
|
1535
1716
|
window.removeEventListener('resize', handleResize);
|
|
1536
1717
|
window.removeEventListener('scroll', handleScroll);
|
|
1537
1718
|
};
|
|
@@ -1628,7 +1809,7 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1628
1809
|
fontWeight: '500',
|
|
1629
1810
|
opacity: canGoPrevious ? 1 : 0.5,
|
|
1630
1811
|
transition: 'all 0.2s ease',
|
|
1631
|
-
}, children: "Previous" })), jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1812
|
+
}, children: "Previous" })), !shouldClickToAdvance && (jsxRuntimeExports.jsx("button", { onClick: (e) => {
|
|
1632
1813
|
e.preventDefault();
|
|
1633
1814
|
e.stopPropagation();
|
|
1634
1815
|
handleNext();
|
|
@@ -1651,7 +1832,18 @@ const TourPopover = React.memo(function TourPopover({ className }) {
|
|
|
1651
1832
|
zIndex: 99999,
|
|
1652
1833
|
}, children: isLastStep
|
|
1653
1834
|
? (popoverConfig.finishLabel || 'Finish')
|
|
1654
|
-
: (popoverConfig.nextLabel || 'Next') })
|
|
1835
|
+
: (popoverConfig.nextLabel || 'Next') })), shouldClickToAdvance && (jsxRuntimeExports.jsx("div", { style: {
|
|
1836
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
1837
|
+
border: '1px solid rgba(59, 130, 246, 0.3)',
|
|
1838
|
+
color: theme.primaryColor || '#3b82f6',
|
|
1839
|
+
padding: '8px 16px',
|
|
1840
|
+
borderRadius: '8px',
|
|
1841
|
+
fontSize: '14px',
|
|
1842
|
+
fontWeight: '500',
|
|
1843
|
+
display: 'flex',
|
|
1844
|
+
alignItems: 'center',
|
|
1845
|
+
gap: '6px',
|
|
1846
|
+
}, children: "\uD83D\uDC46 Click the highlighted element to continue" }))] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
|
|
1655
1847
|
position: 'absolute',
|
|
1656
1848
|
width: '12px',
|
|
1657
1849
|
height: '12px',
|