@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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/browser/upgrade.ts
|
|
2
|
+
var DEFAULT_UPGRADE_TIMEOUT_MS = 2e3;
|
|
3
|
+
function withTimeout(promise, timeoutMs) {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
let settled = false;
|
|
6
|
+
const timer = setTimeout(() => {
|
|
7
|
+
if (!settled) {
|
|
8
|
+
settled = true;
|
|
9
|
+
resolve(false);
|
|
10
|
+
}
|
|
11
|
+
}, timeoutMs);
|
|
12
|
+
promise.then(
|
|
13
|
+
() => {
|
|
14
|
+
if (!settled) {
|
|
15
|
+
settled = true;
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
resolve(true);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
() => {
|
|
21
|
+
if (!settled) {
|
|
22
|
+
settled = true;
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
resolve(true);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function collectCustomElementTags(root) {
|
|
31
|
+
const tags = /* @__PURE__ */ new Set();
|
|
32
|
+
const rootEl = "documentElement" in root ? root.documentElement : root;
|
|
33
|
+
if (!rootEl) return [];
|
|
34
|
+
const consider = (el) => {
|
|
35
|
+
if (el.localName && el.localName.includes("-")) {
|
|
36
|
+
tags.add(el.localName);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
consider(rootEl);
|
|
40
|
+
const all = rootEl.querySelectorAll("*");
|
|
41
|
+
all.forEach(consider);
|
|
42
|
+
return Array.from(tags);
|
|
43
|
+
}
|
|
44
|
+
function collectUpdateCompletables(root) {
|
|
45
|
+
const rootEl = "documentElement" in root ? root.documentElement : root;
|
|
46
|
+
if (!rootEl) return [];
|
|
47
|
+
const promises = [];
|
|
48
|
+
const consider = (el) => {
|
|
49
|
+
const uc = el.updateComplete;
|
|
50
|
+
if (uc && typeof uc.then === "function") {
|
|
51
|
+
promises.push(uc);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
consider(rootEl);
|
|
55
|
+
rootEl.querySelectorAll("*").forEach(consider);
|
|
56
|
+
return promises;
|
|
57
|
+
}
|
|
58
|
+
async function awaitCustomElementsReady(root, timeoutMs = DEFAULT_UPGRADE_TIMEOUT_MS) {
|
|
59
|
+
const warnings = [];
|
|
60
|
+
const hasCustomElements = typeof customElements !== "undefined" && customElements !== null;
|
|
61
|
+
if (!hasCustomElements) {
|
|
62
|
+
return warnings;
|
|
63
|
+
}
|
|
64
|
+
const tags = collectCustomElementTags(root);
|
|
65
|
+
await Promise.all(
|
|
66
|
+
tags.map(async (tag) => {
|
|
67
|
+
if (customElements.get(tag)) return;
|
|
68
|
+
const defined = await withTimeout(customElements.whenDefined(tag), timeoutMs);
|
|
69
|
+
if (!defined) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
tag,
|
|
72
|
+
message: `Custom element "${tag}" did not upgrade within ${timeoutMs}ms; analysis may reflect a pre-hydration state.`
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
const updateCompletables = collectUpdateCompletables(root);
|
|
78
|
+
if (updateCompletables.length > 0) {
|
|
79
|
+
await withTimeout(Promise.all(updateCompletables), timeoutMs);
|
|
80
|
+
}
|
|
81
|
+
return warnings;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
awaitCustomElementsReady
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=chunk-WYFXGXYU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/browser/upgrade.ts"],"sourcesContent":["/**\n * Custom-element upgrade awaiting.\n *\n * Runs in a real browser (needs `customElements`). Waits for custom elements\n * under a root to be defined and, where supported, to finish rendering\n * (Lit's `updateComplete`) before analysis/capture proceeds. Bounded by a\n * timeout so it never blocks indefinitely; unresolved tags become warnings.\n */\n\nexport interface UpgradeWarning {\n message: string;\n tag: string;\n}\n\nconst DEFAULT_UPGRADE_TIMEOUT_MS = 2000;\n\n/**\n * Resolve a promise, or resolve with a sentinel when the timeout elapses.\n * Returns true if the promise resolved in time, false on timeout.\n */\nfunction withTimeout(promise: Promise<unknown>, timeoutMs: number): Promise<boolean> {\n return new Promise<boolean>(resolve => {\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n resolve(false);\n }\n }, timeoutMs);\n\n promise.then(\n () => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(true);\n }\n },\n () => {\n // A rejected upgrade promise still means \"no longer pending\".\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(true);\n }\n }\n );\n });\n}\n\n/**\n * Collect distinct custom-element tag names present under a root.\n * A custom element tag always contains a hyphen.\n */\nfunction collectCustomElementTags(root: Document | Element): string[] {\n const tags = new Set<string>();\n const rootEl: Element | null =\n 'documentElement' in root ? (root as Document).documentElement : (root as Element);\n if (!rootEl) return [];\n\n const consider = (el: Element) => {\n if (el.localName && el.localName.includes('-')) {\n tags.add(el.localName);\n }\n };\n\n consider(rootEl);\n const all = rootEl.querySelectorAll('*');\n all.forEach(consider);\n\n return Array.from(tags);\n}\n\n/**\n * Collect elements that expose an `updateComplete` thenable (e.g. Lit).\n */\nfunction collectUpdateCompletables(root: Document | Element): Promise<unknown>[] {\n const rootEl: Element | null =\n 'documentElement' in root ? (root as Document).documentElement : (root as Element);\n if (!rootEl) return [];\n\n const promises: Promise<unknown>[] = [];\n const consider = (el: Element) => {\n const uc = (el as unknown as { updateComplete?: unknown }).updateComplete;\n if (uc && typeof (uc as Promise<unknown>).then === 'function') {\n promises.push(uc as Promise<unknown>);\n }\n };\n\n consider(rootEl);\n rootEl.querySelectorAll('*').forEach(consider);\n return promises;\n}\n\n/**\n * Wait for custom elements under `root` to upgrade and render.\n *\n * - Awaits `customElements.whenDefined(tag)` for each custom tag (bounded).\n * - Awaits any `updateComplete` promises found (bounded).\n * - On timeout, resolves anyway and returns a warning per unresolved tag.\n * - Never blocks indefinitely. No-op when `customElements` is unavailable.\n */\nexport async function awaitCustomElementsReady(\n root: Document | Element,\n timeoutMs: number = DEFAULT_UPGRADE_TIMEOUT_MS\n): Promise<UpgradeWarning[]> {\n const warnings: UpgradeWarning[] = [];\n\n const hasCustomElements =\n typeof customElements !== 'undefined' && customElements !== null;\n\n if (!hasCustomElements) {\n return warnings;\n }\n\n const tags = collectCustomElementTags(root);\n\n // Await definitions.\n await Promise.all(\n tags.map(async tag => {\n // Already defined — nothing to wait for.\n if (customElements.get(tag)) return;\n const defined = await withTimeout(customElements.whenDefined(tag), timeoutMs);\n if (!defined) {\n warnings.push({\n tag,\n message: `Custom element \"${tag}\" did not upgrade within ${timeoutMs}ms; analysis may reflect a pre-hydration state.`,\n });\n }\n })\n );\n\n // Await render completion where components expose it (Lit et al.).\n const updateCompletables = collectUpdateCompletables(root);\n if (updateCompletables.length > 0) {\n await withTimeout(Promise.all(updateCompletables), timeoutMs);\n }\n\n return warnings;\n}\n"],"mappings":";AAcA,IAAM,6BAA6B;AAMnC,SAAS,YAAY,SAA2B,WAAqC;AACnF,SAAO,IAAI,QAAiB,aAAW;AACrC,QAAI,UAAU;AACd,UAAM,QAAQ,WAAW,MAAM;AAC7B,UAAI,CAAC,SAAS;AACZ,kBAAU;AACV,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF,GAAG,SAAS;AAEZ,YAAQ;AAAA,MACN,MAAM;AACJ,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,uBAAa,KAAK;AAClB,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF;AAAA,MACA,MAAM;AAEJ,YAAI,CAAC,SAAS;AACZ,oBAAU;AACV,uBAAa,KAAK;AAClB,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAMA,SAAS,yBAAyB,MAAoC;AACpE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SACJ,qBAAqB,OAAQ,KAAkB,kBAAmB;AACpE,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,WAAW,CAAC,OAAgB;AAChC,QAAI,GAAG,aAAa,GAAG,UAAU,SAAS,GAAG,GAAG;AAC9C,WAAK,IAAI,GAAG,SAAS;AAAA,IACvB;AAAA,EACF;AAEA,WAAS,MAAM;AACf,QAAM,MAAM,OAAO,iBAAiB,GAAG;AACvC,MAAI,QAAQ,QAAQ;AAEpB,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,0BAA0B,MAA8C;AAC/E,QAAM,SACJ,qBAAqB,OAAQ,KAAkB,kBAAmB;AACpE,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,WAA+B,CAAC;AACtC,QAAM,WAAW,CAAC,OAAgB;AAChC,UAAM,KAAM,GAA+C;AAC3D,QAAI,MAAM,OAAQ,GAAwB,SAAS,YAAY;AAC7D,eAAS,KAAK,EAAsB;AAAA,IACtC;AAAA,EACF;AAEA,WAAS,MAAM;AACf,SAAO,iBAAiB,GAAG,EAAE,QAAQ,QAAQ;AAC7C,SAAO;AACT;AAUA,eAAsB,yBACpB,MACA,YAAoB,4BACO;AAC3B,QAAM,WAA6B,CAAC;AAEpC,QAAM,oBACJ,OAAO,mBAAmB,eAAe,mBAAmB;AAE9D,MAAI,CAAC,mBAAmB;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,yBAAyB,IAAI;AAG1C,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,OAAM,QAAO;AAEpB,UAAI,eAAe,IAAI,GAAG,EAAG;AAC7B,YAAM,UAAU,MAAM,YAAY,eAAe,YAAY,GAAG,GAAG,SAAS;AAC5E,UAAI,CAAC,SAAS;AACZ,iBAAS,KAAK;AAAA,UACZ;AAAA,UACA,SAAS,mBAAmB,GAAG,4BAA4B,SAAS;AAAA,QACtE,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,qBAAqB,0BAA0B,IAAI;AACzD,MAAI,mBAAmB,SAAS,GAAG;AACjC,UAAM,YAAY,QAAQ,IAAI,kBAAkB,GAAG,SAAS;AAAA,EAC9D;AAEA,SAAO;AACT;","names":[]}
|
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,18 +1088,74 @@ 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) {
|
|
1111
|
+
return collectAccessibleChildren(element, warnings);
|
|
1112
|
+
}
|
|
1113
|
+
function collectAccessibleChildren(element, warnings) {
|
|
1077
1114
|
const children = [];
|
|
1078
1115
|
if (element.shadowRoot) {
|
|
1079
1116
|
for (const child of Array.from(element.shadowRoot.childNodes)) {
|
|
1080
|
-
|
|
1117
|
+
processShadowNode(child, children, warnings);
|
|
1081
1118
|
}
|
|
1119
|
+
return children;
|
|
1120
|
+
}
|
|
1121
|
+
if (element.localName && element.localName.includes("-") && element.childNodes.length === 0) {
|
|
1122
|
+
warnings.push({
|
|
1123
|
+
message: `Custom element <${element.localName}> has no accessible children and may have a closed shadow root; its content could not be analyzed.`,
|
|
1124
|
+
element
|
|
1125
|
+
});
|
|
1082
1126
|
}
|
|
1083
1127
|
for (const child of Array.from(element.childNodes)) {
|
|
1084
1128
|
processChildNode(child, children, warnings);
|
|
1085
1129
|
}
|
|
1086
1130
|
return children;
|
|
1087
1131
|
}
|
|
1132
|
+
function processShadowNode(node, children, warnings) {
|
|
1133
|
+
if (node.nodeType === 1) {
|
|
1134
|
+
const element = node;
|
|
1135
|
+
if (element.localName === "slot") {
|
|
1136
|
+
const slot = element;
|
|
1137
|
+
let assigned = [];
|
|
1138
|
+
if (typeof slot.assignedNodes === "function") {
|
|
1139
|
+
try {
|
|
1140
|
+
assigned = Array.from(slot.assignedNodes({ flatten: true }));
|
|
1141
|
+
} catch {
|
|
1142
|
+
assigned = [];
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (assigned.length > 0) {
|
|
1146
|
+
for (const assignedNode of assigned) {
|
|
1147
|
+
processChildNode(assignedNode, children, warnings);
|
|
1148
|
+
}
|
|
1149
|
+
} else {
|
|
1150
|
+
for (const child of Array.from(slot.childNodes)) {
|
|
1151
|
+
processShadowNode(child, children, warnings);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
processChildNode(node, children, warnings);
|
|
1158
|
+
}
|
|
1088
1159
|
function buildAccessibilityTree(rootElement, sourceHash) {
|
|
1089
1160
|
const warnings = [];
|
|
1090
1161
|
if (isHidden(rootElement)) {
|
|
@@ -1141,6 +1212,30 @@ function buildAccessibilityTreeWithSelector(rootElement, selector, sourceHash) {
|
|
|
1141
1212
|
}
|
|
1142
1213
|
return results;
|
|
1143
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
|
+
}
|
|
1144
1239
|
function buildNodeRecursive(element, warnings) {
|
|
1145
1240
|
if (isHidden(element)) {
|
|
1146
1241
|
return null;
|
|
@@ -1177,15 +1272,15 @@ function buildNodeRecursive(element, warnings) {
|
|
|
1177
1272
|
element: w.element
|
|
1178
1273
|
})));
|
|
1179
1274
|
const focusInfo = extractFocusInfo(element);
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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;
|
|
1184
1281
|
}
|
|
1185
1282
|
}
|
|
1186
|
-
|
|
1187
|
-
processChildNode(child, children, warnings);
|
|
1188
|
-
}
|
|
1283
|
+
assignImplicitSetPosition(children);
|
|
1189
1284
|
const node = {
|
|
1190
1285
|
role: roleResult.role,
|
|
1191
1286
|
name: nameResult.name,
|
|
@@ -1198,15 +1293,7 @@ function buildNodeRecursive(element, warnings) {
|
|
|
1198
1293
|
return node;
|
|
1199
1294
|
}
|
|
1200
1295
|
function createGenericContainer(rootElement, warnings) {
|
|
1201
|
-
const children =
|
|
1202
|
-
if (rootElement.shadowRoot) {
|
|
1203
|
-
for (const child of Array.from(rootElement.shadowRoot.childNodes)) {
|
|
1204
|
-
processChildNode(child, children, warnings);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
for (const child of Array.from(rootElement.childNodes)) {
|
|
1208
|
-
processChildNode(child, children, warnings);
|
|
1209
|
-
}
|
|
1296
|
+
const children = collectAccessibleChildren(rootElement, warnings);
|
|
1210
1297
|
return {
|
|
1211
1298
|
role: "generic",
|
|
1212
1299
|
name: "",
|
|
@@ -1828,8 +1915,45 @@ function renderNodeVoiceOver(node, announcements, c) {
|
|
|
1828
1915
|
renderNodeVoiceOver(child, announcements, c);
|
|
1829
1916
|
}
|
|
1830
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
|
+
}
|
|
1831
1929
|
function formatNodeVoiceOver(node, c) {
|
|
1930
|
+
if (isSilentContainerVoiceOver(node)) {
|
|
1931
|
+
return "";
|
|
1932
|
+
}
|
|
1832
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
|
+
}
|
|
1833
1957
|
const roleFirst = shouldAnnounceRoleFirst(node.role);
|
|
1834
1958
|
if (roleFirst) {
|
|
1835
1959
|
const roleText = formatRoleVoiceOver(node);
|
|
@@ -1858,6 +1982,9 @@ function formatNodeVoiceOver(node, c) {
|
|
|
1858
1982
|
parts.push(valueText);
|
|
1859
1983
|
}
|
|
1860
1984
|
}
|
|
1985
|
+
if (node.state.posinset !== void 0 && node.state.setsize !== void 0) {
|
|
1986
|
+
parts.push(`${node.state.posinset} of ${node.state.setsize}`);
|
|
1987
|
+
}
|
|
1861
1988
|
if (node.description) {
|
|
1862
1989
|
parts.push(c.description(node.description));
|
|
1863
1990
|
}
|
|
@@ -1893,7 +2020,7 @@ function formatRoleVoiceOver(node) {
|
|
|
1893
2020
|
case "list":
|
|
1894
2021
|
return "list";
|
|
1895
2022
|
case "listitem":
|
|
1896
|
-
return "
|
|
2023
|
+
return "";
|
|
1897
2024
|
case "navigation":
|
|
1898
2025
|
return "navigation";
|
|
1899
2026
|
case "main":
|
|
@@ -1949,6 +2076,18 @@ function formatRoleVoiceOver(node) {
|
|
|
1949
2076
|
return "progress indicator";
|
|
1950
2077
|
case "status":
|
|
1951
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";
|
|
1952
2091
|
case "group":
|
|
1953
2092
|
return node.name ? "group" : "";
|
|
1954
2093
|
case "document":
|
|
@@ -1969,6 +2108,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1969
2108
|
if (node.state.checked !== void 0) {
|
|
1970
2109
|
if (node.state.checked === "mixed") {
|
|
1971
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");
|
|
1972
2115
|
} else {
|
|
1973
2116
|
states.push(node.state.checked ? "checked" : "unchecked");
|
|
1974
2117
|
}
|
|
@@ -1977,10 +2120,10 @@ function formatStatesVoiceOver(node) {
|
|
|
1977
2120
|
if (node.state.pressed === "mixed") {
|
|
1978
2121
|
states.push("mixed");
|
|
1979
2122
|
} else {
|
|
1980
|
-
states.push(node.state.pressed ? "
|
|
2123
|
+
states.push(node.state.pressed ? "selected" : "unselected");
|
|
1981
2124
|
}
|
|
1982
2125
|
}
|
|
1983
|
-
if (node.state.selected !== void 0) {
|
|
2126
|
+
if (node.state.selected !== void 0 && node.role !== "tab") {
|
|
1984
2127
|
states.push(node.state.selected ? "selected" : "unselected");
|
|
1985
2128
|
}
|
|
1986
2129
|
if (node.state.disabled) {
|