@reticular/speakable 1.6.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/README.md +86 -2
- package/dist/browser/index.d.ts +58 -0
- package/dist/browser/index.js +4100 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chunk-WYFXGXYU.js +87 -0
- package/dist/chunk-WYFXGXYU.js.map +1 -0
- package/dist/cli.js +164 -21
- package/dist/cli.js.map +1 -1
- package/dist/harness/index.d.ts +57 -0
- package/dist/harness/index.js +176 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +168 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +164 -21
- package/dist/mcp.js.map +1 -1
- package/dist/speakable-browser.global.js +10 -0
- package/dist/speakable-browser.global.js.map +1 -0
- package/dist/storybook/dist/chunk-3SVI6IQE.js +86 -0
- package/dist/storybook/dist/{chunk-CL3DJBD7.js → chunk-JSBRDJBE.js} +1 -11
- package/dist/storybook/dist/chunk-QVJRQZT5.js +16 -0
- package/dist/storybook/dist/index.js +6 -1
- package/dist/storybook/dist/manager.js +404 -5
- package/dist/storybook/dist/preview.js +2241 -49
- package/dist/storybook/dist/upgrade-5GL42JH4.js +7 -0
- package/dist/types-Blh0EnZF.d.ts +213 -0
- package/dist/upgrade-53M5VYGH.js +7 -0
- package/dist/upgrade-53M5VYGH.js.map +1 -0
- package/package.json +13 -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,18 +863,74 @@ 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) {
|
|
886
|
+
return collectAccessibleChildren(element, warnings);
|
|
887
|
+
}
|
|
888
|
+
function collectAccessibleChildren(element, warnings) {
|
|
852
889
|
const children = [];
|
|
853
890
|
if (element.shadowRoot) {
|
|
854
891
|
for (const child of Array.from(element.shadowRoot.childNodes)) {
|
|
855
|
-
|
|
892
|
+
processShadowNode(child, children, warnings);
|
|
856
893
|
}
|
|
894
|
+
return children;
|
|
895
|
+
}
|
|
896
|
+
if (element.localName && element.localName.includes("-") && element.childNodes.length === 0) {
|
|
897
|
+
warnings.push({
|
|
898
|
+
message: `Custom element <${element.localName}> has no accessible children and may have a closed shadow root; its content could not be analyzed.`,
|
|
899
|
+
element
|
|
900
|
+
});
|
|
857
901
|
}
|
|
858
902
|
for (const child of Array.from(element.childNodes)) {
|
|
859
903
|
processChildNode(child, children, warnings);
|
|
860
904
|
}
|
|
861
905
|
return children;
|
|
862
906
|
}
|
|
907
|
+
function processShadowNode(node, children, warnings) {
|
|
908
|
+
if (node.nodeType === 1) {
|
|
909
|
+
const element = node;
|
|
910
|
+
if (element.localName === "slot") {
|
|
911
|
+
const slot = element;
|
|
912
|
+
let assigned = [];
|
|
913
|
+
if (typeof slot.assignedNodes === "function") {
|
|
914
|
+
try {
|
|
915
|
+
assigned = Array.from(slot.assignedNodes({ flatten: true }));
|
|
916
|
+
} catch {
|
|
917
|
+
assigned = [];
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (assigned.length > 0) {
|
|
921
|
+
for (const assignedNode of assigned) {
|
|
922
|
+
processChildNode(assignedNode, children, warnings);
|
|
923
|
+
}
|
|
924
|
+
} else {
|
|
925
|
+
for (const child of Array.from(slot.childNodes)) {
|
|
926
|
+
processShadowNode(child, children, warnings);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
processChildNode(node, children, warnings);
|
|
933
|
+
}
|
|
863
934
|
function buildAccessibilityTree(rootElement, sourceHash) {
|
|
864
935
|
const warnings = [];
|
|
865
936
|
if (isHidden(rootElement)) {
|
|
@@ -916,6 +987,30 @@ function buildAccessibilityTreeWithSelector(rootElement, selector, sourceHash) {
|
|
|
916
987
|
}
|
|
917
988
|
return results;
|
|
918
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
|
+
}
|
|
919
1014
|
function buildNodeRecursive(element, warnings) {
|
|
920
1015
|
if (isHidden(element)) {
|
|
921
1016
|
return null;
|
|
@@ -952,15 +1047,15 @@ function buildNodeRecursive(element, warnings) {
|
|
|
952
1047
|
element: w.element
|
|
953
1048
|
})));
|
|
954
1049
|
const focusInfo = extractFocusInfo(element);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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;
|
|
959
1056
|
}
|
|
960
1057
|
}
|
|
961
|
-
|
|
962
|
-
processChildNode(child, children, warnings);
|
|
963
|
-
}
|
|
1058
|
+
assignImplicitSetPosition(children);
|
|
964
1059
|
const node = {
|
|
965
1060
|
role: roleResult.role,
|
|
966
1061
|
name: nameResult.name,
|
|
@@ -973,15 +1068,7 @@ function buildNodeRecursive(element, warnings) {
|
|
|
973
1068
|
return node;
|
|
974
1069
|
}
|
|
975
1070
|
function createGenericContainer(rootElement, warnings) {
|
|
976
|
-
const children =
|
|
977
|
-
if (rootElement.shadowRoot) {
|
|
978
|
-
for (const child of Array.from(rootElement.shadowRoot.childNodes)) {
|
|
979
|
-
processChildNode(child, children, warnings);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
for (const child of Array.from(rootElement.childNodes)) {
|
|
983
|
-
processChildNode(child, children, warnings);
|
|
984
|
-
}
|
|
1071
|
+
const children = collectAccessibleChildren(rootElement, warnings);
|
|
985
1072
|
return {
|
|
986
1073
|
role: "generic",
|
|
987
1074
|
name: "",
|
|
@@ -1465,8 +1552,45 @@ function renderNodeVoiceOver(node, announcements, c) {
|
|
|
1465
1552
|
renderNodeVoiceOver(child, announcements, c);
|
|
1466
1553
|
}
|
|
1467
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
|
+
}
|
|
1468
1566
|
function formatNodeVoiceOver(node, c) {
|
|
1567
|
+
if (isSilentContainerVoiceOver(node)) {
|
|
1568
|
+
return "";
|
|
1569
|
+
}
|
|
1469
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
|
+
}
|
|
1470
1594
|
const roleFirst = shouldAnnounceRoleFirst(node.role);
|
|
1471
1595
|
if (roleFirst) {
|
|
1472
1596
|
const roleText = formatRoleVoiceOver(node);
|
|
@@ -1495,6 +1619,9 @@ function formatNodeVoiceOver(node, c) {
|
|
|
1495
1619
|
parts.push(valueText);
|
|
1496
1620
|
}
|
|
1497
1621
|
}
|
|
1622
|
+
if (node.state.posinset !== void 0 && node.state.setsize !== void 0) {
|
|
1623
|
+
parts.push(`${node.state.posinset} of ${node.state.setsize}`);
|
|
1624
|
+
}
|
|
1498
1625
|
if (node.description) {
|
|
1499
1626
|
parts.push(c.description(node.description));
|
|
1500
1627
|
}
|
|
@@ -1530,7 +1657,7 @@ function formatRoleVoiceOver(node) {
|
|
|
1530
1657
|
case "list":
|
|
1531
1658
|
return "list";
|
|
1532
1659
|
case "listitem":
|
|
1533
|
-
return "
|
|
1660
|
+
return "";
|
|
1534
1661
|
case "navigation":
|
|
1535
1662
|
return "navigation";
|
|
1536
1663
|
case "main":
|
|
@@ -1586,6 +1713,18 @@ function formatRoleVoiceOver(node) {
|
|
|
1586
1713
|
return "progress indicator";
|
|
1587
1714
|
case "status":
|
|
1588
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";
|
|
1589
1728
|
case "group":
|
|
1590
1729
|
return node.name ? "group" : "";
|
|
1591
1730
|
case "document":
|
|
@@ -1606,6 +1745,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1606
1745
|
if (node.state.checked !== void 0) {
|
|
1607
1746
|
if (node.state.checked === "mixed") {
|
|
1608
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");
|
|
1609
1752
|
} else {
|
|
1610
1753
|
states.push(node.state.checked ? "checked" : "unchecked");
|
|
1611
1754
|
}
|
|
@@ -1614,10 +1757,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1614
1757
|
if (node.state.pressed === "mixed") {
|
|
1615
1758
|
states.push("mixed");
|
|
1616
1759
|
} else {
|
|
1617
|
-
states.push(node.state.pressed ? "
|
|
1760
|
+
states.push(node.state.pressed ? "selected" : "unselected");
|
|
1618
1761
|
}
|
|
1619
1762
|
}
|
|
1620
|
-
if (node.state.selected !== void 0) {
|
|
1763
|
+
if (node.state.selected !== void 0 && node.role !== "tab") {
|
|
1621
1764
|
states.push(node.state.selected ? "selected" : "unselected");
|
|
1622
1765
|
}
|
|
1623
1766
|
if (node.state.disabled) {
|