@votodigital-onpeui/react 0.1.54 → 0.1.56

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
@@ -1541,6 +1541,61 @@ var unlockBodyScroll = (id, enabled) => {
1541
1541
  document.body.style.overflow = "";
1542
1542
  }
1543
1543
  };
1544
+ var FOCUSABLE_SELECTOR = [
1545
+ "a[href]",
1546
+ "area[href]",
1547
+ "button:not([disabled])",
1548
+ 'input:not([disabled]):not([type="hidden"])',
1549
+ "select:not([disabled])",
1550
+ "textarea:not([disabled])",
1551
+ "iframe",
1552
+ "object",
1553
+ "embed",
1554
+ '[tabindex]:not([tabindex="-1"])',
1555
+ '[contenteditable="true"]'
1556
+ ].join(",");
1557
+ var FOCUS_GUARD_ATTRIBUTE = "data-focus-guard";
1558
+ var focusGuardStyle = {
1559
+ position: "absolute",
1560
+ width: "1px",
1561
+ height: "1px",
1562
+ padding: 0,
1563
+ margin: 0,
1564
+ overflow: "hidden",
1565
+ clip: "rect(0, 0, 0, 0)",
1566
+ whiteSpace: "nowrap",
1567
+ border: 0
1568
+ };
1569
+ var isElementVisible = (element) => {
1570
+ const style = globalThis.getComputedStyle(element);
1571
+ return style.visibility !== "hidden" && style.display !== "none" && element.offsetParent !== null;
1572
+ };
1573
+ var getFocusableElements = (wrapper, includeWrapper = true) => {
1574
+ let focusable = Array.from(wrapper.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
1575
+ (el) => !el.hasAttribute(FOCUS_GUARD_ATTRIBUTE) && isElementVisible(el) && el.tabIndex !== -1
1576
+ );
1577
+ if (includeWrapper && wrapper.tabIndex >= 0) {
1578
+ focusable = [wrapper, ...focusable];
1579
+ }
1580
+ return focusable;
1581
+ };
1582
+ var focusWrapper = (wrapper, options) => {
1583
+ if (wrapper.tabIndex >= 0) {
1584
+ wrapper.focus(options);
1585
+ return;
1586
+ }
1587
+ const focusable = getFocusableElements(wrapper, false);
1588
+ focusable[0]?.focus(options);
1589
+ };
1590
+ var focusEdgeElement = (wrapper, position, options) => {
1591
+ const focusable = getFocusableElements(wrapper, false);
1592
+ const target = position === "last" ? focusable.at(-1) : focusable[0];
1593
+ if (target) {
1594
+ target.focus(options);
1595
+ return;
1596
+ }
1597
+ focusWrapper(wrapper, options);
1598
+ };
1544
1599
  var Modal = ({
1545
1600
  isOpen,
1546
1601
  onClose,
@@ -1569,6 +1624,16 @@ var Modal = ({
1569
1624
  const modalRef = react.useRef(null);
1570
1625
  const contentRef = react.useRef(null);
1571
1626
  const previousActiveElement = react.useRef(null);
1627
+ const handleStartFocusGuard = () => {
1628
+ const wrapper = modalRef.current;
1629
+ if (!wrapper) return;
1630
+ focusEdgeElement(wrapper, "last", { preventScroll: true });
1631
+ };
1632
+ const handleEndFocusGuard = () => {
1633
+ const wrapper = modalRef.current;
1634
+ if (!wrapper) return;
1635
+ focusEdgeElement(wrapper, "first", { preventScroll: true });
1636
+ };
1572
1637
  const [mounted, setMounted] = react.useState(false);
1573
1638
  const [visible, setVisible] = react.useState(false);
1574
1639
  const [cachedChildren, setCachedChildren] = react.useState(children);
@@ -1618,52 +1683,21 @@ var Modal = ({
1618
1683
  [10, 50, 100, 200].forEach((d) => setTimeout(resetScroll, d));
1619
1684
  }, [isOpen]);
1620
1685
  react.useEffect(() => {
1621
- let focusOutWrapper = null;
1622
1686
  const pendingTasks = [];
1623
- const isElementVisible = (element) => {
1624
- const style = globalThis.getComputedStyle(element);
1625
- return style.visibility !== "hidden" && style.display !== "none" && element.offsetParent !== null;
1626
- };
1627
- const getFocusableElements = (wrapper) => {
1628
- const selector = [
1629
- "a[href]",
1630
- "area[href]",
1631
- "button:not([disabled])",
1632
- 'input:not([disabled]):not([type="hidden"])',
1633
- "select:not([disabled])",
1634
- "textarea:not([disabled])",
1635
- "iframe",
1636
- "object",
1637
- "embed",
1638
- '[tabindex]:not([tabindex="-1"])',
1639
- '[contenteditable="true"]'
1640
- ].join(",");
1641
- let focusable = Array.from(
1642
- wrapper.querySelectorAll(selector)
1643
- ).filter((el) => isElementVisible(el) && el.tabIndex !== -1);
1644
- if (wrapper.tabIndex >= 0) {
1645
- focusable = [wrapper, ...focusable];
1646
- }
1647
- return focusable;
1648
- };
1649
- const handleFocusOut = (e) => {
1687
+ const handleDocumentFocusIn = (e) => {
1650
1688
  if (!isOpen || disableFocus) return;
1651
1689
  const wrapper = modalRef.current;
1652
- if (!wrapper) return;
1653
- const relatedTarget = e.relatedTarget;
1654
- if (relatedTarget && !wrapper.contains(relatedTarget)) {
1655
- setTimeout(() => {
1656
- const currentActive = document.activeElement;
1657
- if (!currentActive || !wrapper.contains(currentActive)) {
1658
- const focusable = getFocusableElements(wrapper);
1659
- if (focusable.length > 0) {
1660
- focusable[focusable.length - 1].focus();
1661
- } else {
1662
- wrapper.focus();
1663
- }
1664
- }
1665
- }, 0);
1690
+ const target = e.target;
1691
+ if (!wrapper || !(target instanceof HTMLElement) || wrapper.contains(target)) {
1692
+ return;
1666
1693
  }
1694
+ requestAnimationFrame(() => {
1695
+ const currentActive = document.activeElement;
1696
+ if (currentActive instanceof HTMLElement && wrapper.contains(currentActive)) {
1697
+ return;
1698
+ }
1699
+ focusWrapper(wrapper, { preventScroll: true });
1700
+ });
1667
1701
  };
1668
1702
  const handleKeyDown = (e) => {
1669
1703
  if (e.key === "Escape" && escapeToClose && !closeDisabled) {
@@ -1682,7 +1716,7 @@ var Modal = ({
1682
1716
  if ((e.key === "ArrowUp" || e.key === "ArrowLeft") && activeIndex2 === 0) {
1683
1717
  e.preventDefault();
1684
1718
  e.stopPropagation();
1685
- if (focusable.length > 1) focusable[focusable.length - 1].focus();
1719
+ if (focusable.length > 1) focusable.at(-1)?.focus();
1686
1720
  else active.focus();
1687
1721
  return;
1688
1722
  }
@@ -1706,7 +1740,7 @@ var Modal = ({
1706
1740
  e.preventDefault();
1707
1741
  if (focusable.length > 0) {
1708
1742
  if (e.key === "ArrowUp" || e.key === "ArrowLeft")
1709
- focusable[focusable.length - 1].focus();
1743
+ focusable.at(-1)?.focus();
1710
1744
  else focusable[0].focus();
1711
1745
  } else {
1712
1746
  wrapper.focus();
@@ -1721,8 +1755,13 @@ var Modal = ({
1721
1755
  return;
1722
1756
  }
1723
1757
  const first = focusable[0];
1724
- const last = focusable[focusable.length - 1];
1758
+ const last = focusable.at(-1);
1725
1759
  const isShift = e.shiftKey;
1760
+ if (!first || !last) {
1761
+ e.preventDefault();
1762
+ wrapper.focus();
1763
+ return;
1764
+ }
1726
1765
  if (!active || !wrapper.contains(active)) {
1727
1766
  e.preventDefault();
1728
1767
  (isShift ? last : first).focus();
@@ -1745,14 +1784,7 @@ var Modal = ({
1745
1784
  if (isOpen && !disableFocus) {
1746
1785
  previousActiveElement.current = document.activeElement;
1747
1786
  const focusInitial = (wrapper) => {
1748
- if (ariaLabelledBy && document.getElementById(ariaLabelledBy)) {
1749
- wrapper.focus({ preventScroll: true });
1750
- return;
1751
- }
1752
- const focusable = getFocusableElements(wrapper);
1753
- const first = focusable[0];
1754
- if (first) first.focus({ preventScroll: true });
1755
- else wrapper.focus();
1787
+ focusWrapper(wrapper, { preventScroll: true });
1756
1788
  };
1757
1789
  const bindFocusManagement = (attempt = 0) => {
1758
1790
  const wrapper = modalRef.current;
@@ -1764,14 +1796,10 @@ var Modal = ({
1764
1796
  }
1765
1797
  return;
1766
1798
  }
1767
- if (focusOutWrapper !== wrapper) {
1768
- focusOutWrapper?.removeEventListener("focusout", handleFocusOut);
1769
- wrapper.addEventListener("focusout", handleFocusOut);
1770
- focusOutWrapper = wrapper;
1771
- }
1772
1799
  focusInitial(wrapper);
1773
1800
  };
1774
1801
  document.addEventListener("keydown", handleKeyDown);
1802
+ document.addEventListener("focusin", handleDocumentFocusIn, true);
1775
1803
  pendingTasks.push(globalThis.setTimeout(() => bindFocusManagement(), 0));
1776
1804
  } else if (isOpen && disableFocus) {
1777
1805
  document.addEventListener("keydown", handleKeyDown);
@@ -1779,7 +1807,7 @@ var Modal = ({
1779
1807
  return () => {
1780
1808
  pendingTasks.forEach((task) => globalThis.clearTimeout(task));
1781
1809
  document.removeEventListener("keydown", handleKeyDown);
1782
- focusOutWrapper?.removeEventListener("focusout", handleFocusOut);
1810
+ document.removeEventListener("focusin", handleDocumentFocusIn, true);
1783
1811
  if (!disableFocus && !disableFocusRestore && previousActiveElement.current) {
1784
1812
  previousActiveElement.current.focus();
1785
1813
  }
@@ -1839,6 +1867,16 @@ var Modal = ({
1839
1867
  "aria-describedby": props["aria-describedby"],
1840
1868
  "aria-label": props["aria-label"],
1841
1869
  children: [
1870
+ /* @__PURE__ */ jsxRuntime.jsx(
1871
+ "span",
1872
+ {
1873
+ tabIndex: disableFocus ? -1 : 0,
1874
+ "aria-hidden": "true",
1875
+ ...{ [FOCUS_GUARD_ATTRIBUTE]: "start" },
1876
+ style: focusGuardStyle,
1877
+ onFocus: handleStartFocusGuard
1878
+ }
1879
+ ),
1842
1880
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: contentRef, className: contentClass, children: isOpen ? children : cachedChildren }),
1843
1881
  closeButton && /* @__PURE__ */ jsxRuntime.jsx(
1844
1882
  "button",
@@ -1849,6 +1887,16 @@ var Modal = ({
1849
1887
  type: "button",
1850
1888
  children: /* @__PURE__ */ jsxRuntime.jsx(IconCloseRadius, { "aria-hidden": "true", className: "w-full h-full" })
1851
1889
  }
1890
+ ),
1891
+ /* @__PURE__ */ jsxRuntime.jsx(
1892
+ "span",
1893
+ {
1894
+ tabIndex: disableFocus ? -1 : 0,
1895
+ "aria-hidden": "true",
1896
+ ...{ [FOCUS_GUARD_ATTRIBUTE]: "end" },
1897
+ style: focusGuardStyle,
1898
+ onFocus: handleEndFocusGuard
1899
+ }
1852
1900
  )
1853
1901
  ]
1854
1902
  }
@@ -2276,7 +2324,13 @@ var ModalLoading = ({
2276
2324
  }
2277
2325
  )
2278
2326
  ] }),
2279
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white leading-normal text-2xl md:text-[64px] text-center mt-10 md:mt-20", children: message })
2327
+ /* @__PURE__ */ jsxRuntime.jsx(
2328
+ "p",
2329
+ {
2330
+ className: `text-white leading-normal text-2xl md:text-[64px] text-center ${spinner ? "mt-5" : "mt-10 md:mt-20"}`,
2331
+ children: message
2332
+ }
2333
+ )
2280
2334
  ]
2281
2335
  }
2282
2336
  );