@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.
package/dist/cli.js CHANGED
@@ -526,6 +526,12 @@ var SUPPORTED_ROLES = [
526
526
  "meter",
527
527
  "progressbar",
528
528
  "status",
529
+ "switch",
530
+ "slider",
531
+ "spinbutton",
532
+ "tab",
533
+ "tablist",
534
+ "tabpanel",
529
535
  "document",
530
536
  "application",
531
537
  "separator"
@@ -622,7 +628,10 @@ var INPUT_TYPE_ROLE_MAP = {
622
628
  "search": "textbox",
623
629
  "tel": "textbox",
624
630
  "url": "textbox",
625
- "number": "textbox"
631
+ // Per ARIA, <input type="number"> has an implicit role of spinbutton
632
+ // (VoiceOver announces it as "stepper").
633
+ "number": "spinbutton",
634
+ "range": "slider"
626
635
  };
627
636
  function computeRole(element) {
628
637
  const warnings = [];
@@ -655,6 +664,11 @@ function computeImplicitRole(element, _warnings) {
655
664
  if (tagName === "input") {
656
665
  return computeInputRole(element);
657
666
  }
667
+ if (tagName === "select") {
668
+ const multiple = element.hasAttribute("multiple");
669
+ const size = parseInt(element.getAttribute("size") || "0", 10);
670
+ return multiple || size > 1 ? "listbox" : "combobox";
671
+ }
658
672
  if (tagName === "a") {
659
673
  return element.hasAttribute("href") ? "link" : null;
660
674
  }
@@ -1064,6 +1078,7 @@ function processChildNode(node, children, warnings) {
1064
1078
  if (node.nodeType === 1) {
1065
1079
  const element = node;
1066
1080
  if (isHidden(element)) return;
1081
+ if (isConsumedLabel(element)) return;
1067
1082
  if (!isAccessible(element)) {
1068
1083
  const grandchildren = collectChildrenFromRolelessElement(element, warnings);
1069
1084
  children.push(...grandchildren);
@@ -1073,6 +1088,25 @@ function processChildNode(node, children, warnings) {
1073
1088
  if (childNode) children.push(childNode);
1074
1089
  }
1075
1090
  }
1091
+ function isConsumedLabel(element) {
1092
+ if (element.tagName.toLowerCase() !== "label") return false;
1093
+ const forId = element.getAttribute("for");
1094
+ if (forId) {
1095
+ const doc = element.ownerDocument;
1096
+ const target = doc?.getElementById(forId);
1097
+ if (target && isLabelableControl(target)) return true;
1098
+ }
1099
+ const wrapped = element.querySelector("input, select, textarea, button, meter, progress");
1100
+ if (wrapped && isLabelableControl(wrapped)) return true;
1101
+ return false;
1102
+ }
1103
+ function isLabelableControl(element) {
1104
+ const tag = element.tagName.toLowerCase();
1105
+ if (tag === "input") {
1106
+ return (element.getAttribute("type") || "text").toLowerCase() !== "hidden";
1107
+ }
1108
+ return tag === "select" || tag === "textarea" || tag === "button" || tag === "meter" || tag === "progress";
1109
+ }
1076
1110
  function collectChildrenFromRolelessElement(element, warnings) {
1077
1111
  return collectAccessibleChildren(element, warnings);
1078
1112
  }
@@ -1178,6 +1212,30 @@ function buildAccessibilityTreeWithSelector(rootElement, selector, sourceHash) {
1178
1212
  }
1179
1213
  return results;
1180
1214
  }
1215
+ var SET_MEMBER_ROLES = /* @__PURE__ */ new Set([
1216
+ "radio",
1217
+ "option",
1218
+ "listitem",
1219
+ "tab"
1220
+ ]);
1221
+ function assignImplicitSetPosition(children) {
1222
+ const totals = /* @__PURE__ */ new Map();
1223
+ for (const child of children) {
1224
+ if (SET_MEMBER_ROLES.has(child.role)) {
1225
+ totals.set(child.role, (totals.get(child.role) ?? 0) + 1);
1226
+ }
1227
+ }
1228
+ const seen = /* @__PURE__ */ new Map();
1229
+ for (const child of children) {
1230
+ if (!SET_MEMBER_ROLES.has(child.role)) continue;
1231
+ const total = totals.get(child.role);
1232
+ if (total < 2) continue;
1233
+ const pos = (seen.get(child.role) ?? 0) + 1;
1234
+ seen.set(child.role, pos);
1235
+ if (child.state.posinset === void 0) child.state.posinset = pos;
1236
+ if (child.state.setsize === void 0) child.state.setsize = total;
1237
+ }
1238
+ }
1181
1239
  function buildNodeRecursive(element, warnings) {
1182
1240
  if (isHidden(element)) {
1183
1241
  return null;
@@ -1214,7 +1272,15 @@ function buildNodeRecursive(element, warnings) {
1214
1272
  element: w.element
1215
1273
  })));
1216
1274
  const focusInfo = extractFocusInfo(element);
1217
- const children = collectAccessibleChildren(element, warnings);
1275
+ let children = collectAccessibleChildren(element, warnings);
1276
+ const isNativeSelect = element.tagName.toLowerCase() === "select";
1277
+ if (isNativeSelect && roleResult.role === "combobox") {
1278
+ children = [];
1279
+ if (stateResult.state.expanded === void 0) {
1280
+ stateResult.state.expanded = false;
1281
+ }
1282
+ }
1283
+ assignImplicitSetPosition(children);
1218
1284
  const node = {
1219
1285
  role: roleResult.role,
1220
1286
  name: nameResult.name,
@@ -1849,8 +1915,45 @@ function renderNodeVoiceOver(node, announcements, c) {
1849
1915
  renderNodeVoiceOver(child, announcements, c);
1850
1916
  }
1851
1917
  }
1918
+ function isValueFirstWidget(role) {
1919
+ return role === "slider" || role === "spinbutton" || role === "combobox";
1920
+ }
1921
+ function isValueBeforeRoleWidget(role) {
1922
+ return role === "progressbar" || role === "meter";
1923
+ }
1924
+ function isSilentContainerVoiceOver(node) {
1925
+ if (node.role !== "list" && node.role !== "status") return false;
1926
+ if (node.name) return false;
1927
+ return node.children.length > 0;
1928
+ }
1852
1929
  function formatNodeVoiceOver(node, c) {
1930
+ if (isSilentContainerVoiceOver(node)) {
1931
+ return "";
1932
+ }
1853
1933
  const parts = [];
1934
+ const valueFirst = isValueFirstWidget(node.role) && node.value !== void 0;
1935
+ if (valueFirst) {
1936
+ const valueText = node.value.text || String(node.value.current);
1937
+ if (valueText) parts.push(valueText);
1938
+ if (node.name) parts.push(c.elementName(node.name));
1939
+ const roleText = formatRoleVoiceOver(node);
1940
+ if (roleText) parts.push(c.roleName(roleText));
1941
+ const stateText2 = formatStatesVoiceOver(node);
1942
+ if (stateText2) parts.push(c.stateName(stateText2));
1943
+ if (node.description) parts.push(c.description(node.description));
1944
+ return parts.join(", ");
1945
+ }
1946
+ if (isValueBeforeRoleWidget(node.role) && node.value !== void 0) {
1947
+ if (node.name) parts.push(c.elementName(node.name));
1948
+ const valueText = node.value.text || String(node.value.current);
1949
+ if (valueText) parts.push(valueText);
1950
+ const roleText = formatRoleVoiceOver(node);
1951
+ if (roleText) parts.push(c.roleName(roleText));
1952
+ const stateText2 = formatStatesVoiceOver(node);
1953
+ if (stateText2) parts.push(c.stateName(stateText2));
1954
+ if (node.description) parts.push(c.description(node.description));
1955
+ return parts.join(", ");
1956
+ }
1854
1957
  const roleFirst = shouldAnnounceRoleFirst(node.role);
1855
1958
  if (roleFirst) {
1856
1959
  const roleText = formatRoleVoiceOver(node);
@@ -1879,6 +1982,9 @@ function formatNodeVoiceOver(node, c) {
1879
1982
  parts.push(valueText);
1880
1983
  }
1881
1984
  }
1985
+ if (node.state.posinset !== void 0 && node.state.setsize !== void 0) {
1986
+ parts.push(`${node.state.posinset} of ${node.state.setsize}`);
1987
+ }
1882
1988
  if (node.description) {
1883
1989
  parts.push(c.description(node.description));
1884
1990
  }
@@ -1914,7 +2020,7 @@ function formatRoleVoiceOver(node) {
1914
2020
  case "list":
1915
2021
  return "list";
1916
2022
  case "listitem":
1917
- return "item";
2023
+ return "";
1918
2024
  case "navigation":
1919
2025
  return "navigation";
1920
2026
  case "main":
@@ -1970,6 +2076,18 @@ function formatRoleVoiceOver(node) {
1970
2076
  return "progress indicator";
1971
2077
  case "status":
1972
2078
  return "status";
2079
+ case "switch":
2080
+ return "switch";
2081
+ case "slider":
2082
+ return "slider";
2083
+ case "spinbutton":
2084
+ return "stepper";
2085
+ case "tab":
2086
+ return "tab";
2087
+ case "tablist":
2088
+ return node.name ? "tab group" : "tab group";
2089
+ case "tabpanel":
2090
+ return "tab panel";
1973
2091
  case "group":
1974
2092
  return node.name ? "group" : "";
1975
2093
  case "document":
@@ -1990,6 +2108,10 @@ function formatStatesVoiceOver(node) {
1990
2108
  if (node.state.checked !== void 0) {
1991
2109
  if (node.state.checked === "mixed") {
1992
2110
  states.push("mixed");
2111
+ } else if (node.role === "switch") {
2112
+ states.push(node.state.checked ? "on" : "off");
2113
+ } else if (node.role === "radio") {
2114
+ if (node.state.checked) states.push("selected");
1993
2115
  } else {
1994
2116
  states.push(node.state.checked ? "checked" : "unchecked");
1995
2117
  }
@@ -1998,10 +2120,10 @@ function formatStatesVoiceOver(node) {
1998
2120
  if (node.state.pressed === "mixed") {
1999
2121
  states.push("mixed");
2000
2122
  } else {
2001
- states.push(node.state.pressed ? "pressed" : "not pressed");
2123
+ states.push(node.state.pressed ? "selected" : "unselected");
2002
2124
  }
2003
2125
  }
2004
- if (node.state.selected !== void 0) {
2126
+ if (node.state.selected !== void 0 && node.role !== "tab") {
2005
2127
  states.push(node.state.selected ? "selected" : "unselected");
2006
2128
  }
2007
2129
  if (node.state.disabled) {