@reticular/speakable 1.6.0 → 1.7.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.
@@ -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
@@ -1074,17 +1074,54 @@ function processChildNode(node, children, warnings) {
1074
1074
  }
1075
1075
  }
1076
1076
  function collectChildrenFromRolelessElement(element, warnings) {
1077
+ return collectAccessibleChildren(element, warnings);
1078
+ }
1079
+ function collectAccessibleChildren(element, warnings) {
1077
1080
  const children = [];
1078
1081
  if (element.shadowRoot) {
1079
1082
  for (const child of Array.from(element.shadowRoot.childNodes)) {
1080
- processChildNode(child, children, warnings);
1083
+ processShadowNode(child, children, warnings);
1081
1084
  }
1085
+ return children;
1086
+ }
1087
+ if (element.localName && element.localName.includes("-") && element.childNodes.length === 0) {
1088
+ warnings.push({
1089
+ message: `Custom element <${element.localName}> has no accessible children and may have a closed shadow root; its content could not be analyzed.`,
1090
+ element
1091
+ });
1082
1092
  }
1083
1093
  for (const child of Array.from(element.childNodes)) {
1084
1094
  processChildNode(child, children, warnings);
1085
1095
  }
1086
1096
  return children;
1087
1097
  }
1098
+ function processShadowNode(node, children, warnings) {
1099
+ if (node.nodeType === 1) {
1100
+ const element = node;
1101
+ if (element.localName === "slot") {
1102
+ const slot = element;
1103
+ let assigned = [];
1104
+ if (typeof slot.assignedNodes === "function") {
1105
+ try {
1106
+ assigned = Array.from(slot.assignedNodes({ flatten: true }));
1107
+ } catch {
1108
+ assigned = [];
1109
+ }
1110
+ }
1111
+ if (assigned.length > 0) {
1112
+ for (const assignedNode of assigned) {
1113
+ processChildNode(assignedNode, children, warnings);
1114
+ }
1115
+ } else {
1116
+ for (const child of Array.from(slot.childNodes)) {
1117
+ processShadowNode(child, children, warnings);
1118
+ }
1119
+ }
1120
+ return;
1121
+ }
1122
+ }
1123
+ processChildNode(node, children, warnings);
1124
+ }
1088
1125
  function buildAccessibilityTree(rootElement, sourceHash) {
1089
1126
  const warnings = [];
1090
1127
  if (isHidden(rootElement)) {
@@ -1177,15 +1214,7 @@ function buildNodeRecursive(element, warnings) {
1177
1214
  element: w.element
1178
1215
  })));
1179
1216
  const focusInfo = extractFocusInfo(element);
1180
- const children = [];
1181
- if (element.shadowRoot) {
1182
- for (const child of Array.from(element.shadowRoot.childNodes)) {
1183
- processChildNode(child, children, warnings);
1184
- }
1185
- }
1186
- for (const child of Array.from(element.childNodes)) {
1187
- processChildNode(child, children, warnings);
1188
- }
1217
+ const children = collectAccessibleChildren(element, warnings);
1189
1218
  const node = {
1190
1219
  role: roleResult.role,
1191
1220
  name: nameResult.name,
@@ -1198,15 +1227,7 @@ function buildNodeRecursive(element, warnings) {
1198
1227
  return node;
1199
1228
  }
1200
1229
  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
- }
1230
+ const children = collectAccessibleChildren(rootElement, warnings);
1210
1231
  return {
1211
1232
  role: "generic",
1212
1233
  name: "",