@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/mcp.js
CHANGED
|
@@ -301,6 +301,12 @@ var SUPPORTED_ROLES = [
|
|
|
301
301
|
"meter",
|
|
302
302
|
"progressbar",
|
|
303
303
|
"status",
|
|
304
|
+
"switch",
|
|
305
|
+
"slider",
|
|
306
|
+
"spinbutton",
|
|
307
|
+
"tab",
|
|
308
|
+
"tablist",
|
|
309
|
+
"tabpanel",
|
|
304
310
|
"document",
|
|
305
311
|
"application",
|
|
306
312
|
"separator"
|
|
@@ -397,7 +403,10 @@ var INPUT_TYPE_ROLE_MAP = {
|
|
|
397
403
|
"search": "textbox",
|
|
398
404
|
"tel": "textbox",
|
|
399
405
|
"url": "textbox",
|
|
400
|
-
"number"
|
|
406
|
+
// Per ARIA, <input type="number"> has an implicit role of spinbutton
|
|
407
|
+
// (VoiceOver announces it as "stepper").
|
|
408
|
+
"number": "spinbutton",
|
|
409
|
+
"range": "slider"
|
|
401
410
|
};
|
|
402
411
|
function computeRole(element) {
|
|
403
412
|
const warnings = [];
|
|
@@ -430,6 +439,11 @@ function computeImplicitRole(element, _warnings) {
|
|
|
430
439
|
if (tagName === "input") {
|
|
431
440
|
return computeInputRole(element);
|
|
432
441
|
}
|
|
442
|
+
if (tagName === "select") {
|
|
443
|
+
const multiple = element.hasAttribute("multiple");
|
|
444
|
+
const size = parseInt(element.getAttribute("size") || "0", 10);
|
|
445
|
+
return multiple || size > 1 ? "listbox" : "combobox";
|
|
446
|
+
}
|
|
433
447
|
if (tagName === "a") {
|
|
434
448
|
return element.hasAttribute("href") ? "link" : null;
|
|
435
449
|
}
|
|
@@ -839,6 +853,7 @@ function processChildNode(node, children, warnings) {
|
|
|
839
853
|
if (node.nodeType === 1) {
|
|
840
854
|
const element = node;
|
|
841
855
|
if (isHidden(element)) return;
|
|
856
|
+
if (isConsumedLabel(element)) return;
|
|
842
857
|
if (!isAccessible(element)) {
|
|
843
858
|
const grandchildren = collectChildrenFromRolelessElement(element, warnings);
|
|
844
859
|
children.push(...grandchildren);
|
|
@@ -848,6 +863,25 @@ function processChildNode(node, children, warnings) {
|
|
|
848
863
|
if (childNode) children.push(childNode);
|
|
849
864
|
}
|
|
850
865
|
}
|
|
866
|
+
function isConsumedLabel(element) {
|
|
867
|
+
if (element.tagName.toLowerCase() !== "label") return false;
|
|
868
|
+
const forId = element.getAttribute("for");
|
|
869
|
+
if (forId) {
|
|
870
|
+
const doc = element.ownerDocument;
|
|
871
|
+
const target = doc?.getElementById(forId);
|
|
872
|
+
if (target && isLabelableControl(target)) return true;
|
|
873
|
+
}
|
|
874
|
+
const wrapped = element.querySelector("input, select, textarea, button, meter, progress");
|
|
875
|
+
if (wrapped && isLabelableControl(wrapped)) return true;
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
function isLabelableControl(element) {
|
|
879
|
+
const tag = element.tagName.toLowerCase();
|
|
880
|
+
if (tag === "input") {
|
|
881
|
+
return (element.getAttribute("type") || "text").toLowerCase() !== "hidden";
|
|
882
|
+
}
|
|
883
|
+
return tag === "select" || tag === "textarea" || tag === "button" || tag === "meter" || tag === "progress";
|
|
884
|
+
}
|
|
851
885
|
function collectChildrenFromRolelessElement(element, warnings) {
|
|
852
886
|
return collectAccessibleChildren(element, warnings);
|
|
853
887
|
}
|
|
@@ -953,6 +987,30 @@ function buildAccessibilityTreeWithSelector(rootElement, selector, sourceHash) {
|
|
|
953
987
|
}
|
|
954
988
|
return results;
|
|
955
989
|
}
|
|
990
|
+
var SET_MEMBER_ROLES = /* @__PURE__ */ new Set([
|
|
991
|
+
"radio",
|
|
992
|
+
"option",
|
|
993
|
+
"listitem",
|
|
994
|
+
"tab"
|
|
995
|
+
]);
|
|
996
|
+
function assignImplicitSetPosition(children) {
|
|
997
|
+
const totals = /* @__PURE__ */ new Map();
|
|
998
|
+
for (const child of children) {
|
|
999
|
+
if (SET_MEMBER_ROLES.has(child.role)) {
|
|
1000
|
+
totals.set(child.role, (totals.get(child.role) ?? 0) + 1);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1004
|
+
for (const child of children) {
|
|
1005
|
+
if (!SET_MEMBER_ROLES.has(child.role)) continue;
|
|
1006
|
+
const total = totals.get(child.role);
|
|
1007
|
+
if (total < 2) continue;
|
|
1008
|
+
const pos = (seen.get(child.role) ?? 0) + 1;
|
|
1009
|
+
seen.set(child.role, pos);
|
|
1010
|
+
if (child.state.posinset === void 0) child.state.posinset = pos;
|
|
1011
|
+
if (child.state.setsize === void 0) child.state.setsize = total;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
956
1014
|
function buildNodeRecursive(element, warnings) {
|
|
957
1015
|
if (isHidden(element)) {
|
|
958
1016
|
return null;
|
|
@@ -989,7 +1047,15 @@ function buildNodeRecursive(element, warnings) {
|
|
|
989
1047
|
element: w.element
|
|
990
1048
|
})));
|
|
991
1049
|
const focusInfo = extractFocusInfo(element);
|
|
992
|
-
|
|
1050
|
+
let children = collectAccessibleChildren(element, warnings);
|
|
1051
|
+
const isNativeSelect = element.tagName.toLowerCase() === "select";
|
|
1052
|
+
if (isNativeSelect && roleResult.role === "combobox") {
|
|
1053
|
+
children = [];
|
|
1054
|
+
if (stateResult.state.expanded === void 0) {
|
|
1055
|
+
stateResult.state.expanded = false;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
assignImplicitSetPosition(children);
|
|
993
1059
|
const node = {
|
|
994
1060
|
role: roleResult.role,
|
|
995
1061
|
name: nameResult.name,
|
|
@@ -1486,8 +1552,45 @@ function renderNodeVoiceOver(node, announcements, c) {
|
|
|
1486
1552
|
renderNodeVoiceOver(child, announcements, c);
|
|
1487
1553
|
}
|
|
1488
1554
|
}
|
|
1555
|
+
function isValueFirstWidget(role) {
|
|
1556
|
+
return role === "slider" || role === "spinbutton" || role === "combobox";
|
|
1557
|
+
}
|
|
1558
|
+
function isValueBeforeRoleWidget(role) {
|
|
1559
|
+
return role === "progressbar" || role === "meter";
|
|
1560
|
+
}
|
|
1561
|
+
function isSilentContainerVoiceOver(node) {
|
|
1562
|
+
if (node.role !== "list" && node.role !== "status") return false;
|
|
1563
|
+
if (node.name) return false;
|
|
1564
|
+
return node.children.length > 0;
|
|
1565
|
+
}
|
|
1489
1566
|
function formatNodeVoiceOver(node, c) {
|
|
1567
|
+
if (isSilentContainerVoiceOver(node)) {
|
|
1568
|
+
return "";
|
|
1569
|
+
}
|
|
1490
1570
|
const parts = [];
|
|
1571
|
+
const valueFirst = isValueFirstWidget(node.role) && node.value !== void 0;
|
|
1572
|
+
if (valueFirst) {
|
|
1573
|
+
const valueText = node.value.text || String(node.value.current);
|
|
1574
|
+
if (valueText) parts.push(valueText);
|
|
1575
|
+
if (node.name) parts.push(c.elementName(node.name));
|
|
1576
|
+
const roleText = formatRoleVoiceOver(node);
|
|
1577
|
+
if (roleText) parts.push(c.roleName(roleText));
|
|
1578
|
+
const stateText2 = formatStatesVoiceOver(node);
|
|
1579
|
+
if (stateText2) parts.push(c.stateName(stateText2));
|
|
1580
|
+
if (node.description) parts.push(c.description(node.description));
|
|
1581
|
+
return parts.join(", ");
|
|
1582
|
+
}
|
|
1583
|
+
if (isValueBeforeRoleWidget(node.role) && node.value !== void 0) {
|
|
1584
|
+
if (node.name) parts.push(c.elementName(node.name));
|
|
1585
|
+
const valueText = node.value.text || String(node.value.current);
|
|
1586
|
+
if (valueText) parts.push(valueText);
|
|
1587
|
+
const roleText = formatRoleVoiceOver(node);
|
|
1588
|
+
if (roleText) parts.push(c.roleName(roleText));
|
|
1589
|
+
const stateText2 = formatStatesVoiceOver(node);
|
|
1590
|
+
if (stateText2) parts.push(c.stateName(stateText2));
|
|
1591
|
+
if (node.description) parts.push(c.description(node.description));
|
|
1592
|
+
return parts.join(", ");
|
|
1593
|
+
}
|
|
1491
1594
|
const roleFirst = shouldAnnounceRoleFirst(node.role);
|
|
1492
1595
|
if (roleFirst) {
|
|
1493
1596
|
const roleText = formatRoleVoiceOver(node);
|
|
@@ -1516,6 +1619,9 @@ function formatNodeVoiceOver(node, c) {
|
|
|
1516
1619
|
parts.push(valueText);
|
|
1517
1620
|
}
|
|
1518
1621
|
}
|
|
1622
|
+
if (node.state.posinset !== void 0 && node.state.setsize !== void 0) {
|
|
1623
|
+
parts.push(`${node.state.posinset} of ${node.state.setsize}`);
|
|
1624
|
+
}
|
|
1519
1625
|
if (node.description) {
|
|
1520
1626
|
parts.push(c.description(node.description));
|
|
1521
1627
|
}
|
|
@@ -1551,7 +1657,7 @@ function formatRoleVoiceOver(node) {
|
|
|
1551
1657
|
case "list":
|
|
1552
1658
|
return "list";
|
|
1553
1659
|
case "listitem":
|
|
1554
|
-
return "
|
|
1660
|
+
return "";
|
|
1555
1661
|
case "navigation":
|
|
1556
1662
|
return "navigation";
|
|
1557
1663
|
case "main":
|
|
@@ -1607,6 +1713,18 @@ function formatRoleVoiceOver(node) {
|
|
|
1607
1713
|
return "progress indicator";
|
|
1608
1714
|
case "status":
|
|
1609
1715
|
return "status";
|
|
1716
|
+
case "switch":
|
|
1717
|
+
return "switch";
|
|
1718
|
+
case "slider":
|
|
1719
|
+
return "slider";
|
|
1720
|
+
case "spinbutton":
|
|
1721
|
+
return "stepper";
|
|
1722
|
+
case "tab":
|
|
1723
|
+
return "tab";
|
|
1724
|
+
case "tablist":
|
|
1725
|
+
return node.name ? "tab group" : "tab group";
|
|
1726
|
+
case "tabpanel":
|
|
1727
|
+
return "tab panel";
|
|
1610
1728
|
case "group":
|
|
1611
1729
|
return node.name ? "group" : "";
|
|
1612
1730
|
case "document":
|
|
@@ -1627,6 +1745,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1627
1745
|
if (node.state.checked !== void 0) {
|
|
1628
1746
|
if (node.state.checked === "mixed") {
|
|
1629
1747
|
states.push("mixed");
|
|
1748
|
+
} else if (node.role === "switch") {
|
|
1749
|
+
states.push(node.state.checked ? "on" : "off");
|
|
1750
|
+
} else if (node.role === "radio") {
|
|
1751
|
+
if (node.state.checked) states.push("selected");
|
|
1630
1752
|
} else {
|
|
1631
1753
|
states.push(node.state.checked ? "checked" : "unchecked");
|
|
1632
1754
|
}
|
|
@@ -1635,10 +1757,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1635
1757
|
if (node.state.pressed === "mixed") {
|
|
1636
1758
|
states.push("mixed");
|
|
1637
1759
|
} else {
|
|
1638
|
-
states.push(node.state.pressed ? "
|
|
1760
|
+
states.push(node.state.pressed ? "selected" : "unselected");
|
|
1639
1761
|
}
|
|
1640
1762
|
}
|
|
1641
|
-
if (node.state.selected !== void 0) {
|
|
1763
|
+
if (node.state.selected !== void 0 && node.role !== "tab") {
|
|
1642
1764
|
states.push(node.state.selected ? "selected" : "unselected");
|
|
1643
1765
|
}
|
|
1644
1766
|
if (node.state.disabled) {
|