@reticular/speakable 1.7.0 → 1.8.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.
@@ -46,6 +46,12 @@ var SUPPORTED_ROLES = [
46
46
  "meter",
47
47
  "progressbar",
48
48
  "status",
49
+ "switch",
50
+ "slider",
51
+ "spinbutton",
52
+ "tab",
53
+ "tablist",
54
+ "tabpanel",
49
55
  "document",
50
56
  "application",
51
57
  "separator"
@@ -345,7 +351,10 @@ var INPUT_TYPE_ROLE_MAP = {
345
351
  "search": "textbox",
346
352
  "tel": "textbox",
347
353
  "url": "textbox",
348
- "number": "textbox"
354
+ // Per ARIA, <input type="number"> has an implicit role of spinbutton
355
+ // (VoiceOver announces it as "stepper").
356
+ "number": "spinbutton",
357
+ "range": "slider"
349
358
  };
350
359
  function computeRole(element) {
351
360
  const warnings = [];
@@ -378,6 +387,11 @@ function computeImplicitRole(element, _warnings) {
378
387
  if (tagName === "input") {
379
388
  return computeInputRole(element);
380
389
  }
390
+ if (tagName === "select") {
391
+ const multiple = element.hasAttribute("multiple");
392
+ const size = parseInt(element.getAttribute("size") || "0", 10);
393
+ return multiple || size > 1 ? "listbox" : "combobox";
394
+ }
381
395
  if (tagName === "a") {
382
396
  return element.hasAttribute("href") ? "link" : null;
383
397
  }
@@ -781,6 +795,7 @@ function processChildNode(node, children, warnings) {
781
795
  if (node.nodeType === 1) {
782
796
  const element = node;
783
797
  if (isHidden(element)) return;
798
+ if (isConsumedLabel(element)) return;
784
799
  if (!isAccessible(element)) {
785
800
  const grandchildren = collectChildrenFromRolelessElement(element, warnings);
786
801
  children.push(...grandchildren);
@@ -790,6 +805,25 @@ function processChildNode(node, children, warnings) {
790
805
  if (childNode) children.push(childNode);
791
806
  }
792
807
  }
808
+ function isConsumedLabel(element) {
809
+ if (element.tagName.toLowerCase() !== "label") return false;
810
+ const forId = element.getAttribute("for");
811
+ if (forId) {
812
+ const doc = element.ownerDocument;
813
+ const target = doc?.getElementById(forId);
814
+ if (target && isLabelableControl(target)) return true;
815
+ }
816
+ const wrapped = element.querySelector("input, select, textarea, button, meter, progress");
817
+ if (wrapped && isLabelableControl(wrapped)) return true;
818
+ return false;
819
+ }
820
+ function isLabelableControl(element) {
821
+ const tag = element.tagName.toLowerCase();
822
+ if (tag === "input") {
823
+ return (element.getAttribute("type") || "text").toLowerCase() !== "hidden";
824
+ }
825
+ return tag === "select" || tag === "textarea" || tag === "button" || tag === "meter" || tag === "progress";
826
+ }
793
827
  function collectChildrenFromRolelessElement(element, warnings) {
794
828
  return collectAccessibleChildren(element, warnings);
795
829
  }
@@ -870,6 +904,30 @@ function buildAccessibilityTree(rootElement, sourceHash) {
870
904
  };
871
905
  return { model, warnings };
872
906
  }
907
+ var SET_MEMBER_ROLES = /* @__PURE__ */ new Set([
908
+ "radio",
909
+ "option",
910
+ "listitem",
911
+ "tab"
912
+ ]);
913
+ function assignImplicitSetPosition(children) {
914
+ const totals = /* @__PURE__ */ new Map();
915
+ for (const child of children) {
916
+ if (SET_MEMBER_ROLES.has(child.role)) {
917
+ totals.set(child.role, (totals.get(child.role) ?? 0) + 1);
918
+ }
919
+ }
920
+ const seen = /* @__PURE__ */ new Map();
921
+ for (const child of children) {
922
+ if (!SET_MEMBER_ROLES.has(child.role)) continue;
923
+ const total = totals.get(child.role);
924
+ if (total < 2) continue;
925
+ const pos = (seen.get(child.role) ?? 0) + 1;
926
+ seen.set(child.role, pos);
927
+ if (child.state.posinset === void 0) child.state.posinset = pos;
928
+ if (child.state.setsize === void 0) child.state.setsize = total;
929
+ }
930
+ }
873
931
  function buildNodeRecursive(element, warnings) {
874
932
  if (isHidden(element)) {
875
933
  return null;
@@ -906,7 +964,15 @@ function buildNodeRecursive(element, warnings) {
906
964
  element: w.element
907
965
  })));
908
966
  const focusInfo = extractFocusInfo(element);
909
- const children = collectAccessibleChildren(element, warnings);
967
+ let children = collectAccessibleChildren(element, warnings);
968
+ const isNativeSelect = element.tagName.toLowerCase() === "select";
969
+ if (isNativeSelect && roleResult.role === "combobox") {
970
+ children = [];
971
+ if (stateResult.state.expanded === void 0) {
972
+ stateResult.state.expanded = false;
973
+ }
974
+ }
975
+ assignImplicitSetPosition(children);
910
976
  const node = {
911
977
  role: roleResult.role,
912
978
  name: nameResult.name,
@@ -1403,8 +1469,45 @@ function renderNodeVoiceOver(node, announcements, c) {
1403
1469
  renderNodeVoiceOver(child, announcements, c);
1404
1470
  }
1405
1471
  }
1472
+ function isValueFirstWidget(role) {
1473
+ return role === "slider" || role === "spinbutton" || role === "combobox";
1474
+ }
1475
+ function isValueBeforeRoleWidget(role) {
1476
+ return role === "progressbar" || role === "meter";
1477
+ }
1478
+ function isSilentContainerVoiceOver(node) {
1479
+ if (node.role !== "list" && node.role !== "status") return false;
1480
+ if (node.name) return false;
1481
+ return node.children.length > 0;
1482
+ }
1406
1483
  function formatNodeVoiceOver(node, c) {
1484
+ if (isSilentContainerVoiceOver(node)) {
1485
+ return "";
1486
+ }
1407
1487
  const parts = [];
1488
+ const valueFirst = isValueFirstWidget(node.role) && node.value !== void 0;
1489
+ if (valueFirst) {
1490
+ const valueText = node.value.text || String(node.value.current);
1491
+ if (valueText) parts.push(valueText);
1492
+ if (node.name) parts.push(c.elementName(node.name));
1493
+ const roleText = formatRoleVoiceOver(node);
1494
+ if (roleText) parts.push(c.roleName(roleText));
1495
+ const stateText2 = formatStatesVoiceOver(node);
1496
+ if (stateText2) parts.push(c.stateName(stateText2));
1497
+ if (node.description) parts.push(c.description(node.description));
1498
+ return parts.join(", ");
1499
+ }
1500
+ if (isValueBeforeRoleWidget(node.role) && node.value !== void 0) {
1501
+ if (node.name) parts.push(c.elementName(node.name));
1502
+ const valueText = node.value.text || String(node.value.current);
1503
+ if (valueText) parts.push(valueText);
1504
+ const roleText = formatRoleVoiceOver(node);
1505
+ if (roleText) parts.push(c.roleName(roleText));
1506
+ const stateText2 = formatStatesVoiceOver(node);
1507
+ if (stateText2) parts.push(c.stateName(stateText2));
1508
+ if (node.description) parts.push(c.description(node.description));
1509
+ return parts.join(", ");
1510
+ }
1408
1511
  const roleFirst = shouldAnnounceRoleFirst(node.role);
1409
1512
  if (roleFirst) {
1410
1513
  const roleText = formatRoleVoiceOver(node);
@@ -1433,6 +1536,9 @@ function formatNodeVoiceOver(node, c) {
1433
1536
  parts.push(valueText);
1434
1537
  }
1435
1538
  }
1539
+ if (node.state.posinset !== void 0 && node.state.setsize !== void 0) {
1540
+ parts.push(`${node.state.posinset} of ${node.state.setsize}`);
1541
+ }
1436
1542
  if (node.description) {
1437
1543
  parts.push(c.description(node.description));
1438
1544
  }
@@ -1468,7 +1574,7 @@ function formatRoleVoiceOver(node) {
1468
1574
  case "list":
1469
1575
  return "list";
1470
1576
  case "listitem":
1471
- return "item";
1577
+ return "";
1472
1578
  case "navigation":
1473
1579
  return "navigation";
1474
1580
  case "main":
@@ -1524,6 +1630,18 @@ function formatRoleVoiceOver(node) {
1524
1630
  return "progress indicator";
1525
1631
  case "status":
1526
1632
  return "status";
1633
+ case "switch":
1634
+ return "switch";
1635
+ case "slider":
1636
+ return "slider";
1637
+ case "spinbutton":
1638
+ return "stepper";
1639
+ case "tab":
1640
+ return "tab";
1641
+ case "tablist":
1642
+ return node.name ? "tab group" : "tab group";
1643
+ case "tabpanel":
1644
+ return "tab panel";
1527
1645
  case "group":
1528
1646
  return node.name ? "group" : "";
1529
1647
  case "document":
@@ -1544,6 +1662,10 @@ function formatStatesVoiceOver(node) {
1544
1662
  if (node.state.checked !== void 0) {
1545
1663
  if (node.state.checked === "mixed") {
1546
1664
  states.push("mixed");
1665
+ } else if (node.role === "switch") {
1666
+ states.push(node.state.checked ? "on" : "off");
1667
+ } else if (node.role === "radio") {
1668
+ if (node.state.checked) states.push("selected");
1547
1669
  } else {
1548
1670
  states.push(node.state.checked ? "checked" : "unchecked");
1549
1671
  }
@@ -1552,10 +1674,10 @@ function formatStatesVoiceOver(node) {
1552
1674
  if (node.state.pressed === "mixed") {
1553
1675
  states.push("mixed");
1554
1676
  } else {
1555
- states.push(node.state.pressed ? "pressed" : "not pressed");
1677
+ states.push(node.state.pressed ? "selected" : "unselected");
1556
1678
  }
1557
1679
  }
1558
- if (node.state.selected !== void 0) {
1680
+ if (node.state.selected !== void 0 && node.role !== "tab") {
1559
1681
  states.push(node.state.selected ? "selected" : "unselected");
1560
1682
  }
1561
1683
  if (node.state.disabled) {