@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/browser/index.js
CHANGED
|
@@ -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"
|
|
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
|
-
|
|
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 "
|
|
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 ? "
|
|
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) {
|