@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/browser/index.js +127 -5
- package/dist/browser/index.js.map +1 -1
- package/dist/cli.js +127 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +127 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +127 -5
- package/dist/mcp.js.map +1 -1
- package/dist/speakable-browser.global.js +9 -9
- package/dist/speakable-browser.global.js.map +1 -1
- package/dist/storybook/dist/preview.js +127 -5
- package/package.json +5 -1
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"
|
|
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
|
-
|
|
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 "
|
|
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 ? "
|
|
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) {
|