@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/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
- const handleOverlayClick = React.useCallback(() => {
1433
- if (state.currentStep?.canSkip !== false) {
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("style", { children: `
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 || !targetElement || !state.currentStep)
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
- updatePosition();
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') })] })] }), position.placement !== 'center' && (jsxRuntimeExports.jsx("div", { style: {
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',