@simplybusiness/mobius 6.5.2 → 6.5.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - af113aa: Fix accessibility issues with Combobox and US State Question
8
+ - e95d7af: Fix React 19 dangerouslySetInnerHTML regression in TextOrHTML
9
+
3
10
  ## 6.5.2
4
11
 
5
12
  ### Patch Changes
package/dist/cjs/index.js CHANGED
@@ -1575,6 +1575,8 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1575
1575
  const listboxId = (0, import_react24.useId)();
1576
1576
  const statusId = (0, import_react24.useId)();
1577
1577
  const blurTimeoutRef = (0, import_react24.useRef)(null);
1578
+ const userInteractedRef = (0, import_react24.useRef)(false);
1579
+ const justSelectedRef = (0, import_react24.useRef)(false);
1578
1580
  const { down } = useBreakpoint();
1579
1581
  const isMobile = down("md");
1580
1582
  const handleFocus = (e) => {
@@ -1584,8 +1586,31 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1584
1586
  clearTimeout(blurTimeoutRef.current);
1585
1587
  blurTimeoutRef.current = null;
1586
1588
  }
1587
- setIsOpen(true);
1589
+ const isNaturalFocus = userInteractedRef.current || e.relatedTarget !== null;
1590
+ if (userInteractedRef.current) {
1591
+ userInteractedRef.current = false;
1592
+ }
1593
+ if (justSelectedRef.current && !isNaturalFocus) {
1594
+ justSelectedRef.current = false;
1595
+ return;
1596
+ }
1597
+ if (isNaturalFocus) {
1598
+ setIsOpen(true);
1599
+ justSelectedRef.current = false;
1600
+ }
1588
1601
  };
1602
+ (0, import_react24.useEffect)(() => {
1603
+ if (!inputRef || typeof inputRef === "function") return;
1604
+ const inputElement = inputRef.current;
1605
+ if (!inputElement) return;
1606
+ const handleMouseDown = () => {
1607
+ userInteractedRef.current = true;
1608
+ };
1609
+ inputElement.addEventListener("mousedown", handleMouseDown);
1610
+ return () => {
1611
+ inputElement.removeEventListener("mousedown", handleMouseDown);
1612
+ };
1613
+ }, [inputRef]);
1589
1614
  useOnUnmount(() => {
1590
1615
  if (blurTimeoutRef.current) {
1591
1616
  clearTimeout(blurTimeoutRef.current);
@@ -1594,7 +1619,11 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1594
1619
  const handleInputChange = (e) => {
1595
1620
  const newValue = e.target.value;
1596
1621
  setInputValue(newValue);
1622
+ justSelectedRef.current = false;
1597
1623
  setIsChanging(true);
1624
+ if (!asyncOptions) {
1625
+ setIsOpen(true);
1626
+ }
1598
1627
  clearHighlight();
1599
1628
  onChange?.(e);
1600
1629
  };
@@ -1607,6 +1636,7 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1607
1636
  return;
1608
1637
  }
1609
1638
  skipNextDebounceRef.current = true;
1639
+ justSelectedRef.current = true;
1610
1640
  setIsChanging(false);
1611
1641
  setIsOpen(false);
1612
1642
  setInputValue(val);
@@ -1637,11 +1667,15 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1637
1667
  return `${listboxId}-option-${highlightedIndex}`;
1638
1668
  };
1639
1669
  const handleBlur = (e) => {
1640
- const typedText = inputValue.trim().toLowerCase();
1641
- const highlightedOption = getHighlightedOption();
1642
- const label = getOptionLabel(highlightedOption);
1643
- if (typedText === label?.toLowerCase()) {
1644
- handleOptionSelect(highlightedOption);
1670
+ if (!justSelectedRef.current) {
1671
+ const typedText = inputValue.trim().toLowerCase();
1672
+ const highlightedOption = getHighlightedOption();
1673
+ const label = getOptionLabel(highlightedOption);
1674
+ if (typedText === label?.toLowerCase()) {
1675
+ setTimeout(() => {
1676
+ handleOptionSelect(highlightedOption);
1677
+ }, 0);
1678
+ }
1645
1679
  }
1646
1680
  blurTimeoutRef.current = setTimeout(() => {
1647
1681
  onBlur?.(e);
@@ -1652,21 +1686,25 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1652
1686
  switch (e.key) {
1653
1687
  case "ArrowDown":
1654
1688
  e.preventDefault();
1689
+ justSelectedRef.current = false;
1655
1690
  setIsOpen(true);
1656
1691
  highlightNextOption();
1657
1692
  break;
1658
1693
  case "ArrowUp":
1659
1694
  e.preventDefault();
1695
+ justSelectedRef.current = false;
1660
1696
  setIsOpen(true);
1661
1697
  highlightPreviousOption();
1662
1698
  break;
1663
1699
  case "Home":
1664
1700
  e.preventDefault();
1701
+ justSelectedRef.current = false;
1665
1702
  setIsOpen(true);
1666
1703
  highlightFirstOption();
1667
1704
  break;
1668
1705
  case "End":
1669
1706
  e.preventDefault();
1707
+ justSelectedRef.current = false;
1670
1708
  setIsOpen(true);
1671
1709
  highlightLastOption();
1672
1710
  break;
@@ -1707,8 +1745,16 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1707
1745
  },
1708
1746
  className
1709
1747
  );
1748
+ const getStatusMessage = () => {
1749
+ if (isLoading) return "Loading options";
1750
+ if (!filteredOptions || filteredOptions.length === 0) {
1751
+ return isChanging ? "No options found" : "";
1752
+ }
1753
+ const count = isOptionGroup(filteredOptions) ? filteredOptions.reduce((sum, group) => sum + group.options.length, 0) : filteredOptions.length;
1754
+ return isOpen && isChanging ? `${count} option${count === 1 ? "" : "s"} available` : "";
1755
+ };
1710
1756
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { id, "data-testid": "mobius-combobox__wrapper", className: classes, children: [
1711
- isLoading && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1757
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1712
1758
  VisuallyHidden,
1713
1759
  {
1714
1760
  role: "status",
@@ -1716,7 +1762,7 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1716
1762
  id: statusId,
1717
1763
  elementType: "div",
1718
1764
  className: "mobius-combobox__status",
1719
- children: "Loading options"
1765
+ children: getStatusMessage()
1720
1766
  }
1721
1767
  ),
1722
1768
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1735,6 +1781,7 @@ var ComboboxInner = (0, import_react24.forwardRef)(
1735
1781
  "aria-describedby": isLoading ? statusId : void 0,
1736
1782
  "aria-autocomplete": "list",
1737
1783
  "aria-haspopup": "listbox",
1784
+ "aria-owns": listboxId,
1738
1785
  "aria-controls": listboxId,
1739
1786
  "aria-expanded": isOpen,
1740
1787
  "aria-activedescendant": highlightedIndex === -1 ? void 0 : getHighlightedOptionId(),
@@ -4382,11 +4429,12 @@ var TextOrHTML = (0, import_react79.forwardRef)(
4382
4429
  ...textProps
4383
4430
  }, ref) => {
4384
4431
  const DangerousComponent = htmlElementType;
4432
+ const dangerousHTML = (0, import_react79.useMemo)(() => ({ __html: text }), [text]);
4385
4433
  const dangerousElement = /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
4386
4434
  DangerousComponent,
4387
4435
  {
4388
4436
  className: htmlClassName,
4389
- dangerouslySetInnerHTML: { __html: text }
4437
+ dangerouslySetInnerHTML: dangerousHTML
4390
4438
  }
4391
4439
  );
4392
4440
  if (textWrapper) {
package/dist/esm/index.js CHANGED
@@ -1464,6 +1464,8 @@ var ComboboxInner = forwardRef6(
1464
1464
  const listboxId = useId4();
1465
1465
  const statusId = useId4();
1466
1466
  const blurTimeoutRef = useRef7(null);
1467
+ const userInteractedRef = useRef7(false);
1468
+ const justSelectedRef = useRef7(false);
1467
1469
  const { down } = useBreakpoint();
1468
1470
  const isMobile = down("md");
1469
1471
  const handleFocus = (e) => {
@@ -1473,8 +1475,31 @@ var ComboboxInner = forwardRef6(
1473
1475
  clearTimeout(blurTimeoutRef.current);
1474
1476
  blurTimeoutRef.current = null;
1475
1477
  }
1476
- setIsOpen(true);
1478
+ const isNaturalFocus = userInteractedRef.current || e.relatedTarget !== null;
1479
+ if (userInteractedRef.current) {
1480
+ userInteractedRef.current = false;
1481
+ }
1482
+ if (justSelectedRef.current && !isNaturalFocus) {
1483
+ justSelectedRef.current = false;
1484
+ return;
1485
+ }
1486
+ if (isNaturalFocus) {
1487
+ setIsOpen(true);
1488
+ justSelectedRef.current = false;
1489
+ }
1477
1490
  };
1491
+ useEffect14(() => {
1492
+ if (!inputRef || typeof inputRef === "function") return;
1493
+ const inputElement = inputRef.current;
1494
+ if (!inputElement) return;
1495
+ const handleMouseDown = () => {
1496
+ userInteractedRef.current = true;
1497
+ };
1498
+ inputElement.addEventListener("mousedown", handleMouseDown);
1499
+ return () => {
1500
+ inputElement.removeEventListener("mousedown", handleMouseDown);
1501
+ };
1502
+ }, [inputRef]);
1478
1503
  useOnUnmount(() => {
1479
1504
  if (blurTimeoutRef.current) {
1480
1505
  clearTimeout(blurTimeoutRef.current);
@@ -1483,7 +1508,11 @@ var ComboboxInner = forwardRef6(
1483
1508
  const handleInputChange = (e) => {
1484
1509
  const newValue = e.target.value;
1485
1510
  setInputValue(newValue);
1511
+ justSelectedRef.current = false;
1486
1512
  setIsChanging(true);
1513
+ if (!asyncOptions) {
1514
+ setIsOpen(true);
1515
+ }
1487
1516
  clearHighlight();
1488
1517
  onChange?.(e);
1489
1518
  };
@@ -1496,6 +1525,7 @@ var ComboboxInner = forwardRef6(
1496
1525
  return;
1497
1526
  }
1498
1527
  skipNextDebounceRef.current = true;
1528
+ justSelectedRef.current = true;
1499
1529
  setIsChanging(false);
1500
1530
  setIsOpen(false);
1501
1531
  setInputValue(val);
@@ -1526,11 +1556,15 @@ var ComboboxInner = forwardRef6(
1526
1556
  return `${listboxId}-option-${highlightedIndex}`;
1527
1557
  };
1528
1558
  const handleBlur = (e) => {
1529
- const typedText = inputValue.trim().toLowerCase();
1530
- const highlightedOption = getHighlightedOption();
1531
- const label = getOptionLabel(highlightedOption);
1532
- if (typedText === label?.toLowerCase()) {
1533
- handleOptionSelect(highlightedOption);
1559
+ if (!justSelectedRef.current) {
1560
+ const typedText = inputValue.trim().toLowerCase();
1561
+ const highlightedOption = getHighlightedOption();
1562
+ const label = getOptionLabel(highlightedOption);
1563
+ if (typedText === label?.toLowerCase()) {
1564
+ setTimeout(() => {
1565
+ handleOptionSelect(highlightedOption);
1566
+ }, 0);
1567
+ }
1534
1568
  }
1535
1569
  blurTimeoutRef.current = setTimeout(() => {
1536
1570
  onBlur?.(e);
@@ -1541,21 +1575,25 @@ var ComboboxInner = forwardRef6(
1541
1575
  switch (e.key) {
1542
1576
  case "ArrowDown":
1543
1577
  e.preventDefault();
1578
+ justSelectedRef.current = false;
1544
1579
  setIsOpen(true);
1545
1580
  highlightNextOption();
1546
1581
  break;
1547
1582
  case "ArrowUp":
1548
1583
  e.preventDefault();
1584
+ justSelectedRef.current = false;
1549
1585
  setIsOpen(true);
1550
1586
  highlightPreviousOption();
1551
1587
  break;
1552
1588
  case "Home":
1553
1589
  e.preventDefault();
1590
+ justSelectedRef.current = false;
1554
1591
  setIsOpen(true);
1555
1592
  highlightFirstOption();
1556
1593
  break;
1557
1594
  case "End":
1558
1595
  e.preventDefault();
1596
+ justSelectedRef.current = false;
1559
1597
  setIsOpen(true);
1560
1598
  highlightLastOption();
1561
1599
  break;
@@ -1596,8 +1634,16 @@ var ComboboxInner = forwardRef6(
1596
1634
  },
1597
1635
  className
1598
1636
  );
1637
+ const getStatusMessage = () => {
1638
+ if (isLoading) return "Loading options";
1639
+ if (!filteredOptions || filteredOptions.length === 0) {
1640
+ return isChanging ? "No options found" : "";
1641
+ }
1642
+ const count = isOptionGroup(filteredOptions) ? filteredOptions.reduce((sum, group) => sum + group.options.length, 0) : filteredOptions.length;
1643
+ return isOpen && isChanging ? `${count} option${count === 1 ? "" : "s"} available` : "";
1644
+ };
1599
1645
  return /* @__PURE__ */ jsxs7("div", { id, "data-testid": "mobius-combobox__wrapper", className: classes, children: [
1600
- isLoading && /* @__PURE__ */ jsx12(
1646
+ /* @__PURE__ */ jsx12(
1601
1647
  VisuallyHidden,
1602
1648
  {
1603
1649
  role: "status",
@@ -1605,7 +1651,7 @@ var ComboboxInner = forwardRef6(
1605
1651
  id: statusId,
1606
1652
  elementType: "div",
1607
1653
  className: "mobius-combobox__status",
1608
- children: "Loading options"
1654
+ children: getStatusMessage()
1609
1655
  }
1610
1656
  ),
1611
1657
  /* @__PURE__ */ jsx12(
@@ -1624,6 +1670,7 @@ var ComboboxInner = forwardRef6(
1624
1670
  "aria-describedby": isLoading ? statusId : void 0,
1625
1671
  "aria-autocomplete": "list",
1626
1672
  "aria-haspopup": "listbox",
1673
+ "aria-owns": listboxId,
1627
1674
  "aria-controls": listboxId,
1628
1675
  "aria-expanded": isOpen,
1629
1676
  "aria-activedescendant": highlightedIndex === -1 ? void 0 : getHighlightedOptionId(),
@@ -4302,7 +4349,7 @@ var TextArea = forwardRef52((props, ref) => {
4302
4349
  TextArea.displayName = "TextArea";
4303
4350
 
4304
4351
  // src/components/TextOrHTML/TextOrHTML.tsx
4305
- import { forwardRef as forwardRef53 } from "react";
4352
+ import { forwardRef as forwardRef53, useMemo as useMemo5 } from "react";
4306
4353
  import { jsx as jsx68 } from "react/jsx-runtime";
4307
4354
  var TextOrHTML = forwardRef53(
4308
4355
  ({
@@ -4313,11 +4360,12 @@ var TextOrHTML = forwardRef53(
4313
4360
  ...textProps
4314
4361
  }, ref) => {
4315
4362
  const DangerousComponent = htmlElementType;
4363
+ const dangerousHTML = useMemo5(() => ({ __html: text }), [text]);
4316
4364
  const dangerousElement = /* @__PURE__ */ jsx68(
4317
4365
  DangerousComponent,
4318
4366
  {
4319
4367
  className: htmlClassName,
4320
- dangerouslySetInnerHTML: { __html: text }
4368
+ dangerouslySetInnerHTML: dangerousHTML
4321
4369
  }
4322
4370
  );
4323
4371
  if (textWrapper) {