halo-agent 2.4.5 → 2.4.6
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/package.json +1 -1
- package/scanAccessibility.js +47 -1
package/package.json
CHANGED
package/scanAccessibility.js
CHANGED
|
@@ -292,6 +292,25 @@ async function enrichFromDom(page, axFields) {
|
|
|
292
292
|
const ownerFrame = window.frameElement;
|
|
293
293
|
const iframeSrc = ownerFrame ? ownerFrame.src || ownerFrame.name || '' : '';
|
|
294
294
|
|
|
295
|
+
// Disjoint label-for recovery. When the input has no matching
|
|
296
|
+
// <label for=id> partner (e.g. Ashby puts data-field-path on a
|
|
297
|
+
// wrapper <div> while the input itself has no id), Chrome's AX
|
|
298
|
+
// algorithm bottoms out at placeholder. We pre-compute a label
|
|
299
|
+
// from a known ATS field-group wrapper here so pickLabel() can
|
|
300
|
+
// prefer it when rawName turns out to be the placeholder.
|
|
301
|
+
let recoveredLabel = '';
|
|
302
|
+
try {
|
|
303
|
+
const wrapperSel = '[data-field-path], [class*="_fieldEntry_"], .ashby-application-form-field-entry, .application-question, [data-question-id]';
|
|
304
|
+
const wrapper = el.closest && el.closest(wrapperSel);
|
|
305
|
+
if (wrapper) {
|
|
306
|
+
const lbl = wrapper.querySelector('label');
|
|
307
|
+
if (lbl) {
|
|
308
|
+
const t = safeText(lbl.textContent || '').replace(/\s*\*\s*$/, '').trim();
|
|
309
|
+
if (t && t.length < 200) recoveredLabel = t;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch {}
|
|
313
|
+
|
|
295
314
|
return {
|
|
296
315
|
mmid,
|
|
297
316
|
tag,
|
|
@@ -301,6 +320,7 @@ async function enrichFromDom(page, axFields) {
|
|
|
301
320
|
id: el.id || '',
|
|
302
321
|
ariaLabel: el.getAttribute('aria-label') || '',
|
|
303
322
|
placeholder: el.placeholder || '',
|
|
323
|
+
recoveredLabel,
|
|
304
324
|
selectorHint,
|
|
305
325
|
currentValue,
|
|
306
326
|
options,
|
|
@@ -349,7 +369,20 @@ function pickLabel(f) {
|
|
|
349
369
|
// Priority: AX name → aria-label → placeholder → text content → field id.
|
|
350
370
|
// (AX name already merges <label for>, aria-labelledby, fieldset/legend
|
|
351
371
|
// walks — we get them all for free.)
|
|
352
|
-
|
|
372
|
+
//
|
|
373
|
+
// Disjoint label-for recovery: when Chrome's AX algorithm bottoms out at
|
|
374
|
+
// the placeholder (because <label for=X> points at a wrapper id and the
|
|
375
|
+
// input itself has no id — Ashby's Location pattern, also any
|
|
376
|
+
// CSS-modules form library), rawName === placeholder. In that case
|
|
377
|
+
// prefer the label we walked from the field-group wrapper.
|
|
378
|
+
const rawName = (f.rawName || '').trim();
|
|
379
|
+
const placeholder = (f.placeholder || '').trim();
|
|
380
|
+
const recovered = (f.recoveredLabel || '').trim();
|
|
381
|
+
if (rawName && placeholder && rawName.toLowerCase() === placeholder.toLowerCase() && recovered) {
|
|
382
|
+
console.log(`[scanAx] recovered label "${recovered}" for placeholder-only field (was: "${rawName}")`);
|
|
383
|
+
return recovered;
|
|
384
|
+
}
|
|
385
|
+
return (rawName || f.ariaLabel || recovered || placeholder || f.textContent || f.id || f.name || '').trim();
|
|
353
386
|
}
|
|
354
387
|
|
|
355
388
|
function pickDescription(f) {
|
|
@@ -394,6 +427,19 @@ async function scanFrameDom(frame, frameUrl) {
|
|
|
394
427
|
const t = alb.split(/\s+/).map((id) => document.getElementById(id)).filter(Boolean).map((e) => safeText(e.textContent)).join(' ').trim();
|
|
395
428
|
if (t) return t;
|
|
396
429
|
}
|
|
430
|
+
// Disjoint label-for recovery via ATS field-group wrapper.
|
|
431
|
+
// Mirrors the recoveredLabel walk in the main-frame DOM evaluator.
|
|
432
|
+
try {
|
|
433
|
+
const wrapperSel = '[data-field-path], [class*="_fieldEntry_"], .ashby-application-form-field-entry, .application-question, [data-question-id]';
|
|
434
|
+
const wrapper = el.closest && el.closest(wrapperSel);
|
|
435
|
+
if (wrapper) {
|
|
436
|
+
const lbl = wrapper.querySelector('label');
|
|
437
|
+
if (lbl && !lbl.contains(el)) {
|
|
438
|
+
const t = safeText(lbl.textContent).replace(/\*$/, '').trim();
|
|
439
|
+
if (t && t.length < 200) return t;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch {}
|
|
397
443
|
let p = el.parentElement, hops = 0;
|
|
398
444
|
while (p && hops < 8) {
|
|
399
445
|
const lbl = p.querySelector('label, legend, h2, h3, h4');
|