@reticlehq/browser 3.8.0 → 3.8.1

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/dom/query.js CHANGED
@@ -1,50 +1,29 @@
1
- import { ALTERNATIVE_TESTID_ATTRS, DEFAULT_TESTID_ATTR, DATA_RETICLE_SOURCE_ATTR, ElementState, QueryBy, REDACTED_VALUE, TRANSPORT_LIMITS, } from '@reticlehq/core';
2
- import { isActionTarget, isElement, isFrame, isInput, isSelect, isTextArea } from './realm.js';
1
+ import { ALTERNATIVE_TESTID_ATTRS, DEFAULT_TESTID_ATTR, ElementState, QueryBy, REDACTED_VALUE, TRANSPORT_LIMITS, } from '@reticlehq/core';
2
+ import { isActionTarget, isElement, isFrame } from './realm.js';
3
3
  import { capturedRootOf } from './shadow-registry.js';
4
- import { getAccessibleName, getRole, describe, getStates, isHiddenByAriaOnly, isInViewport, isRenderedOutsideAriaHidden, isVisible, } from './a11y.js';
4
+ import { getAccessibleName, getRole, describe, getStates } from './a11y.js';
5
+ import { isHiddenByAriaOnly, isInViewport, isRenderedOutsideAriaHidden, isVisible, } from './visibility.js';
5
6
  import { dialogCovering, isIgnored, isReticleOverlay, isWordsIgnored } from './dom-ignore.js';
6
7
  import { isSensitiveKey } from '../security/serialization.js';
7
8
  import { declaredTestids } from '../registry/capabilities.js';
8
- import { identifyComponent } from '../registry/stores/adapters.js';
9
9
  import { refs } from './addressing/refs.js';
10
10
  import { getTestIdAttr, readTestId, testIdSelector } from './addressing/testid-attr.js';
11
11
  import { stableSelectorFor } from './stable-selector.js';
12
- import { exactVisibleText, fuzzyVisibleText, normaliseVisibleText, comparableText, foldTypography, paintedCase, textMatches, unreadableMatch, } from './text-match.js';
12
+ import { fuzzyVisibleText, normaliseVisibleText, comparableText, foldTypography, textMatches, unreadableMatch, } from './text-match.js';
13
13
  import { pageRoute } from '../security/page-location.js';
14
- const SOURCE_ATTR = DATA_RETICLE_SOURCE_ATTR;
14
+ import { elementsUnder, findIn, isNonRendered, queryByRoleAndName } from './query-finders.js';
15
15
  const MAX_PRESENT_TESTIDS = 12;
16
- const MAX_COMPONENT_CANDIDATES = 2000;
17
- const componentCandidateSelector = () => `[${SOURCE_ATTR}], ${testIdSelector()}, button, a, input, select, textarea, [role]`;
18
- function elementsUnder(container) {
19
- const self = Node.ELEMENT_NODE === container.nodeType ? [container] : [];
20
- return [...self, ...Array.from(container.querySelectorAll('*'))];
21
- }
22
- const NON_RENDERED_TAGS = ['script', 'style', 'template', 'noscript'];
23
- function isNonRendered(el) {
24
- return NON_RENDERED_TAGS.includes(el.tagName.toLowerCase());
25
- }
26
- function directText(el) {
27
- if (isInput(el)) {
28
- const type = el.type.toLowerCase();
29
- if ('submit' === type || 'button' === type || 'reset' === type)
30
- return el.value;
31
- }
32
- if (isNonRendered(el))
33
- return '';
34
- return Array.from(el.childNodes)
35
- .filter((node) => Node.TEXT_NODE === node.nodeType)
36
- .map((node) => node.textContent ?? '')
37
- .join('');
38
- }
39
16
  function visibleTextOf(el, memo) {
40
- if (isNonRendered(el) || isWordsIgnored(el))
17
+ if (isNonRendered(el) || isWordsIgnored(el)) {
41
18
  return '';
19
+ }
42
20
  const visible = isVisible(el, memo);
43
21
  const apart = laysOutApart(el);
44
22
  let text = '';
45
23
  for (const node of Array.from(el.childNodes)) {
46
- if (Node.TEXT_NODE === node.nodeType && visible)
24
+ if (Node.TEXT_NODE === node.nodeType && visible) {
47
25
  text += node.textContent ?? '';
26
+ }
48
27
  else if (isElement(node)) {
49
28
  const own = visibleTextOf(node, memo);
50
29
  text += apart || ownBox(node) ? ` ${own} ` : own;
@@ -60,185 +39,24 @@ function ownBox(el) {
60
39
  const display = el.ownerDocument.defaultView?.getComputedStyle(el).display ?? 'inline';
61
40
  return !display.startsWith('inline') && 'contents' !== display;
62
41
  }
63
- function semanticNameTarget(el) {
64
- if (isInput(el) || isTextArea(el) || isSelect(el))
65
- return true;
66
- const tag = el.tagName.toLowerCase();
67
- return ('button' === tag ||
68
- 'meter' === tag ||
69
- 'output' === tag ||
70
- 'progress' === tag ||
71
- el.hasAttribute('aria-label') ||
72
- el.hasAttribute('aria-labelledby'));
73
- }
74
42
  function resolveContainer(scope) {
75
- if (scope === undefined)
43
+ if (scope === undefined) {
76
44
  return { container: document.body, scopeMissing: false };
45
+ }
77
46
  const byRef = refs.resolve(scope);
78
- if (isActionTarget(byRef))
47
+ if (isActionTarget(byRef)) {
79
48
  return { container: byRef, scopeMissing: false };
49
+ }
80
50
  try {
81
51
  const found = document.querySelector(scope);
82
- if (isActionTarget(found))
52
+ if (isActionTarget(found)) {
83
53
  return { container: found, scopeMissing: false };
54
+ }
84
55
  }
85
56
  catch {
86
57
  }
87
58
  return { container: null, scopeMissing: true };
88
59
  }
89
- function findBySource(container, source) {
90
- const prefix = `${source.file}:${source.line}:`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
91
- try {
92
- return Array.from(container.querySelectorAll(`[${SOURCE_ATTR}^="${prefix}"]`));
93
- }
94
- catch {
95
- return [];
96
- }
97
- }
98
- function findByComponentName(container, component) {
99
- const out = [];
100
- let scanned = 0;
101
- for (const el of Array.from(container.querySelectorAll(componentCandidateSelector()))) {
102
- if (scanned >= MAX_COMPONENT_CANDIDATES)
103
- break;
104
- scanned += 1;
105
- const info = identifyComponent(el);
106
- if (info !== null && info.componentStack[0] === component)
107
- out.push(el);
108
- }
109
- return out;
110
- }
111
- function findByComponent(container, query) {
112
- if (query.source !== undefined) {
113
- const bySource = findBySource(container, query.source);
114
- if (bySource.length > 0)
115
- return bySource;
116
- }
117
- if (query.component !== undefined && query.component.length > 0) {
118
- return findByComponentName(container, query.component);
119
- }
120
- return [];
121
- }
122
- function matchesRole(actual, queried) {
123
- if (actual === queried)
124
- return true;
125
- if ('textbox' === queried && 'searchbox' === actual)
126
- return true;
127
- return false;
128
- }
129
- const OPTION_ROLE = 'option';
130
- const HTML_OPTION_TAG = 'option';
131
- const HTML_SELECT_TAG = 'select';
132
- const VISUALLY_HIDDEN_MAX_PX = 1;
133
- export function isHiddenNativeOption(el) {
134
- if (HTML_OPTION_TAG !== el.localName)
135
- return false;
136
- const select = el.closest(HTML_SELECT_TAG);
137
- if (!isSelect(select))
138
- return false;
139
- if (!isVisible(select))
140
- return true;
141
- return isVisuallyHiddenBox(select);
142
- }
143
- function isVisuallyHiddenBox(select) {
144
- const box = select.getBoundingClientRect();
145
- return (box.width > 0 &&
146
- box.height > 0 &&
147
- box.width <= VISUALLY_HIDDEN_MAX_PX &&
148
- box.height <= VISUALLY_HIDDEN_MAX_PX);
149
- }
150
- function preferVisibleActionableOption(role, matches) {
151
- if (OPTION_ROLE !== role)
152
- return matches;
153
- const kept = matches.filter((el) => !isHiddenNativeOption(el));
154
- return kept.length === matches.length ? matches : kept;
155
- }
156
- function queryByRoleAndName(container, role, name) {
157
- const matched = elementsUnder(container).filter((el) => matchesRole(getRole(el), role) &&
158
- (name === undefined || exactVisibleText(getAccessibleName(el), name)));
159
- return preferVisibleActionableOption(role, matched);
160
- }
161
- function queryByText(container, value, query) {
162
- return elementsUnder(container).filter((el) => {
163
- const own = directText(el);
164
- if (textMatches(own, query, value))
165
- return true;
166
- if (true !== query.matchCase || !fuzzyVisibleText(own, value))
167
- return false;
168
- const painted = paintedCase(el, own);
169
- return painted !== undefined && textMatches(painted, query, value);
170
- });
171
- }
172
- function queryByLabel(container, value) {
173
- const expected = comparableText(value).toLowerCase();
174
- const exact = [];
175
- const fuzzy = [];
176
- for (const el of elementsUnder(container)) {
177
- if (!semanticNameTarget(el))
178
- continue;
179
- const name = comparableText(getAccessibleName(el)).toLowerCase();
180
- if (!name.includes(expected))
181
- continue;
182
- (name === expected ? exact : fuzzy).push(el);
183
- }
184
- return exact.length > 0 ? exact : fuzzy;
185
- }
186
- function queryByPlaceholder(container, value) {
187
- return elementsUnder(container).filter((el) => {
188
- const placeholder = el.getAttribute('placeholder');
189
- return placeholder !== null && fuzzyVisibleText(placeholder, value);
190
- });
191
- }
192
- function queryByTestId(container, value) {
193
- return elementsUnder(container).filter((el) => readTestId(el) === value);
194
- }
195
- function queryByAlt(container, value) {
196
- return elementsUnder(container).filter((el) => {
197
- const alt = el.getAttribute('alt');
198
- return alt !== null && fuzzyVisibleText(alt, value);
199
- });
200
- }
201
- function findIn(container, query) {
202
- const by = query.by;
203
- const value = query.value;
204
- if (by !== undefined && value !== undefined) {
205
- switch (by) {
206
- case QueryBy.ROLE:
207
- return queryByRoleAndName(container, value, query.name);
208
- case QueryBy.TEXT:
209
- return queryByText(container, value, query);
210
- case QueryBy.LABEL:
211
- return queryByLabel(container, value);
212
- case QueryBy.PLACEHOLDER:
213
- return queryByPlaceholder(container, value);
214
- case QueryBy.TESTID:
215
- return queryByTestId(container, value);
216
- case QueryBy.ALT:
217
- return queryByAlt(container, value);
218
- case QueryBy.COMPONENT:
219
- return findByComponent(container, { ...query, component: query.component ?? value });
220
- default:
221
- throw new Error(`unsupported query strategy '${String(by)}' - use one of: ${Object.values(QueryBy).join(', ')}`);
222
- }
223
- }
224
- if (query.component !== undefined || query.source !== undefined) {
225
- return findByComponent(container, query);
226
- }
227
- if (query.role !== undefined) {
228
- return queryByRoleAndName(container, query.role, query.name);
229
- }
230
- if (query.text !== undefined)
231
- return queryByText(container, query.text, query);
232
- if (query.label !== undefined)
233
- return queryByLabel(container, query.label);
234
- if (query.placeholder !== undefined)
235
- return queryByPlaceholder(container, query.placeholder);
236
- if (query.testid !== undefined)
237
- return queryByTestId(container, query.testid);
238
- if (query.alt !== undefined)
239
- return queryByAlt(container, query.alt);
240
- return [];
241
- }
242
60
  function readableFrameBody(frame) {
243
61
  try {
244
62
  return frame.contentDocument?.body ?? null;
@@ -258,13 +76,16 @@ function embeddedRootsUnder(root) {
258
76
  walk(shadow, depth);
259
77
  continue;
260
78
  }
261
- if (depth >= FRAME_DEPTH_MAX)
79
+ if (depth >= FRAME_DEPTH_MAX) {
262
80
  continue;
263
- if (!isFrame(el))
81
+ }
82
+ if (!isFrame(el)) {
264
83
  continue;
84
+ }
265
85
  const body = readableFrameBody(el);
266
- if (null === body)
86
+ if (null === body) {
267
87
  continue;
88
+ }
268
89
  found.push(body);
269
90
  walk(body, depth + 1);
270
91
  }
@@ -274,11 +95,13 @@ function embeddedRootsUnder(root) {
274
95
  }
275
96
  function findCandidates(query) {
276
97
  const { container, scopeMissing } = resolveContainer(query.scope);
277
- if (null === container)
98
+ if (null === container) {
278
99
  return { candidates: [], scopeMissing: true };
100
+ }
279
101
  if (true === query.self) {
280
- if (query.scope === undefined)
102
+ if (query.scope === undefined) {
281
103
  return { candidates: [], scopeMissing };
104
+ }
282
105
  if (query.text !== undefined &&
283
106
  !textMatches(visibleTextOf(container, new Map()), query, query.text)) {
284
107
  return { candidates: [], scopeMissing };
@@ -290,17 +113,20 @@ function findCandidates(query) {
290
113
  const ignored = wantedTextOf(query) === undefined ? isIgnored : isWordsIgnored;
291
114
  const collect = (els) => {
292
115
  for (const el of els) {
293
- if (seen.has(el))
116
+ if (seen.has(el)) {
294
117
  continue;
295
- if (ignored(el))
118
+ }
119
+ if (ignored(el)) {
296
120
  continue;
121
+ }
297
122
  seen.add(el);
298
123
  out.push(el);
299
124
  }
300
125
  };
301
126
  collect(findIn(container, query));
302
- for (const embedded of embeddedRootsUnder(container))
127
+ for (const embedded of embeddedRootsUnder(container)) {
303
128
  collect(findIn(embedded, query));
129
+ }
304
130
  return { candidates: out, scopeMissing };
305
131
  }
306
132
  export const ATTR_VALUE_MAX = 512;
@@ -309,15 +135,17 @@ function projectAttrs(el, keys) {
309
135
  const out = {};
310
136
  for (const key of keys.slice(0, ATTR_KEYS_MAX)) {
311
137
  const raw = el.getAttribute(key);
312
- if (null === raw)
138
+ if (null === raw) {
313
139
  continue;
140
+ }
314
141
  out[key] = isSensitiveKey(key) ? REDACTED_VALUE : raw.slice(0, ATTR_VALUE_MAX);
315
142
  }
316
143
  return Object.keys(out).length > 0 ? out : undefined;
317
144
  }
318
145
  function inState(el, state, memo) {
319
- if (ElementState.IN_VIEWPORT === state)
146
+ if (ElementState.IN_VIEWPORT === state) {
320
147
  return isInViewport(el, memo);
148
+ }
321
149
  return getStates(el, isVisible(el, memo)).includes(state);
322
150
  }
323
151
  const MAX_DESCRIBED = TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS;
@@ -340,8 +168,9 @@ export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = tr
340
168
  !base.states.includes(ElementState.IN_VIEWPORT)) {
341
169
  base = { ...base, states: [...base.states, ElementState.IN_VIEWPORT] };
342
170
  }
343
- if (attrs === undefined || 0 === attrs.length)
171
+ if (attrs === undefined || 0 === attrs.length) {
344
172
  return base;
173
+ }
345
174
  const projected = projectAttrs(el, attrs);
346
175
  return projected === undefined ? base : { ...base, attrs: projected };
347
176
  });
@@ -359,8 +188,9 @@ export function matchQuery(query, state, limit = MAX_DESCRIBED, wholePhrase = tr
359
188
  }
360
189
  function resolveLabelledBy(el) {
361
190
  const ids = el.getAttribute('aria-labelledby');
362
- if (null === ids)
191
+ if (null === ids) {
363
192
  return undefined;
193
+ }
364
194
  const text = ids
365
195
  .split(/\s+/)
366
196
  .map((id) => id.length > 0 ? (el.ownerDocument.getElementById(id)?.textContent?.trim() ?? '') : '')
@@ -392,8 +222,9 @@ function buildPresentRegions(query) {
392
222
  for (const role of CONTAINER_ROLES) {
393
223
  const containers = queryByRoleAndName(container, role, undefined);
394
224
  for (const el of containers) {
395
- if (isReticleOverlay(el))
225
+ if (isReticleOverlay(el)) {
396
226
  continue;
227
+ }
397
228
  const name = el.getAttribute('aria-label') ??
398
229
  resolveLabelledBy(el) ??
399
230
  readTestId(el) ??
@@ -401,8 +232,9 @@ function buildPresentRegions(query) {
401
232
  const children = el.querySelectorAll('[role]');
402
233
  const sample = [];
403
234
  for (const child of Array.from(children)) {
404
- if (sample.length >= 3)
235
+ if (sample.length >= 3) {
405
236
  break;
237
+ }
406
238
  const childRole = child.getAttribute('role');
407
239
  const childName = child.getAttribute('aria-label') ??
408
240
  readTestId(child) ??
@@ -413,11 +245,13 @@ function buildPresentRegions(query) {
413
245
  }
414
246
  }
415
247
  const region = { role, childCount: children.length, sample };
416
- if (name !== undefined && name.length > 0)
248
+ if (name !== undefined && name.length > 0) {
417
249
  region.name = name;
250
+ }
418
251
  regions.push(region);
419
- if (regions.length >= 10)
252
+ if (regions.length >= 10) {
420
253
  return regions;
254
+ }
421
255
  }
422
256
  }
423
257
  return regions;
@@ -427,13 +261,16 @@ function splitTextOwner(container, wanted, query) {
427
261
  let bestDepth = -1;
428
262
  const memo = new Map();
429
263
  for (const el of elementsUnder(container)) {
430
- if (isIgnored(el))
264
+ if (isIgnored(el)) {
431
265
  continue;
432
- if (!textMatches(visibleTextOf(el, memo), query, wanted))
266
+ }
267
+ if (!textMatches(visibleTextOf(el, memo), query, wanted)) {
433
268
  continue;
269
+ }
434
270
  let depth = 0;
435
- for (let parent = el.parentElement; parent !== null; parent = parent.parentElement)
271
+ for (let parent = el.parentElement; parent !== null; parent = parent.parentElement) {
436
272
  depth++;
273
+ }
437
274
  if (depth > bestDepth) {
438
275
  best = el;
439
276
  bestDepth = depth;
@@ -443,16 +280,19 @@ function splitTextOwner(container, wanted, query) {
443
280
  }
444
281
  function caseOnlyMatch(container, wanted) {
445
282
  const target = comparableText(wanted).toLowerCase();
446
- if (0 === target.length)
283
+ if (0 === target.length) {
447
284
  return undefined;
285
+ }
448
286
  const memo = new Map();
449
287
  for (const el of elementsUnder(container)) {
450
- if (isIgnored(el))
288
+ if (isIgnored(el)) {
451
289
  continue;
290
+ }
452
291
  const text = normaliseVisibleText(visibleTextOf(el, memo));
453
292
  const at = foldTypography(text).toLowerCase().indexOf(target);
454
- if (-1 !== at)
293
+ if (-1 !== at) {
455
294
  return text.slice(at, at + target.length);
295
+ }
456
296
  }
457
297
  return undefined;
458
298
  }
@@ -468,71 +308,88 @@ function textOnly(query) {
468
308
  Object.keys(query).every((key) => TEXT_ONLY_FIELDS.has(key)));
469
309
  }
470
310
  function wantedTextOf(query) {
471
- if (query.text !== undefined)
311
+ if (query.text !== undefined) {
472
312
  return query.text;
313
+ }
473
314
  return QueryBy.TEXT === query.by ? query.value : undefined;
474
315
  }
475
316
  const MAX_NAME_NEAR_MISSES = 5;
476
317
  function nameNearMisses(container, query) {
477
318
  const role = QueryBy.ROLE === query.by ? query.value : query.role;
478
319
  const wanted = query.name;
479
- if (role === undefined || wanted === undefined || 0 === wanted.length)
320
+ if (role === undefined || wanted === undefined || 0 === wanted.length) {
480
321
  return [];
322
+ }
481
323
  const target = comparableText(wanted).toLowerCase();
482
- if (0 === target.length)
324
+ if (0 === target.length) {
483
325
  return [];
326
+ }
484
327
  const out = [];
485
328
  for (const el of elementsUnder(container)) {
486
- if (isIgnored(el) || getRole(el) !== role)
329
+ if (isIgnored(el) || getRole(el) !== role) {
487
330
  continue;
331
+ }
488
332
  const name = normaliseVisibleText(getAccessibleName(el));
489
- if (0 === name.length)
333
+ if (0 === name.length) {
490
334
  continue;
335
+ }
491
336
  const folded = foldTypography(name).toLowerCase();
492
- if (!folded.includes(target) && !target.includes(folded))
337
+ if (!folded.includes(target) && !target.includes(folded)) {
493
338
  continue;
494
- if (!out.includes(name))
339
+ }
340
+ if (!out.includes(name)) {
495
341
  out.push(name);
496
- if (out.length >= MAX_NAME_NEAR_MISSES)
342
+ }
343
+ if (out.length >= MAX_NAME_NEAR_MISSES) {
497
344
  break;
345
+ }
498
346
  }
499
347
  return out;
500
348
  }
501
349
  function testidFoundUnder(container, query) {
502
- if (QueryBy.TESTID !== query.by || undefined === query.value)
350
+ if (QueryBy.TESTID !== query.by || undefined === query.value) {
503
351
  return undefined;
352
+ }
504
353
  const configured = getTestIdAttr();
505
354
  for (const attr of [DEFAULT_TESTID_ATTR, ...ALTERNATIVE_TESTID_ATTRS]) {
506
- if (attr === configured)
355
+ if (attr === configured) {
507
356
  continue;
357
+ }
508
358
  const found = elementsUnder(container).some((el) => el.getAttribute(attr) === query.value && !isIgnored(el));
509
- if (found)
359
+ if (found) {
510
360
  return attr;
361
+ }
511
362
  }
512
363
  return undefined;
513
364
  }
514
365
  function textInAriaHidden(container, wanted) {
515
366
  let inAriaHidden = false;
516
367
  for (const el of elementsUnder(container)) {
517
- if (isIgnored(el))
368
+ if (isIgnored(el)) {
518
369
  continue;
519
- if (!isHiddenByAriaOnly(el))
370
+ }
371
+ if (!isHiddenByAriaOnly(el)) {
520
372
  continue;
373
+ }
521
374
  if (fuzzyVisibleText(el.textContent ?? '', wanted)) {
522
375
  inAriaHidden = true;
523
376
  break;
524
377
  }
525
378
  }
526
- if (!inAriaHidden)
379
+ if (!inAriaHidden) {
527
380
  return false;
381
+ }
528
382
  const memo = new Map();
529
383
  for (const el of elementsUnder(container)) {
530
- if (isIgnored(el))
384
+ if (isIgnored(el)) {
531
385
  continue;
532
- if (!isRenderedOutsideAriaHidden(el))
386
+ }
387
+ if (!isRenderedOutsideAriaHidden(el)) {
533
388
  continue;
534
- if (fuzzyVisibleText(visibleTextOf(el, memo), wanted))
389
+ }
390
+ if (fuzzyVisibleText(visibleTextOf(el, memo), wanted)) {
535
391
  return false;
392
+ }
536
393
  }
537
394
  return true;
538
395
  }
@@ -542,14 +399,17 @@ function buildEmptyHint(query, state, diagnose = true) {
542
399
  const present = [];
543
400
  const seen = new Set();
544
401
  for (const el of Array.from(all)) {
545
- if (isIgnored(el))
402
+ if (isIgnored(el)) {
546
403
  continue;
404
+ }
547
405
  const id = readTestId(el);
548
- if (null === id || 0 === id.length || seen.has(id))
406
+ if (null === id || 0 === id.length || seen.has(id)) {
549
407
  continue;
408
+ }
550
409
  seen.add(id);
551
- if (present.length < MAX_PRESENT_TESTIDS)
410
+ if (present.length < MAX_PRESENT_TESTIDS) {
552
411
  present.push(id);
412
+ }
553
413
  }
554
414
  const registered = declaredTestids();
555
415
  const knownEmptyState = present.some((id) => registered.includes(id));
@@ -571,23 +431,27 @@ function buildEmptyHint(query, state, diagnose = true) {
571
431
  }
572
432
  else if (true === query.matchCase) {
573
433
  const shown = caseOnlyMatch(container, wanted);
574
- if (shown !== undefined)
434
+ if (shown !== undefined) {
575
435
  hint.caseMiss = shown;
436
+ }
576
437
  }
577
438
  }
578
439
  if (diagnose &&
579
440
  wanted !== undefined &&
580
441
  hint.splitText === undefined &&
581
442
  (state === undefined || ElementState.VISIBLE === state || ElementState.IN_VIEWPORT === state)) {
582
- if (textInAriaHidden(container, wanted))
443
+ if (textInAriaHidden(container, wanted)) {
583
444
  hint.ariaHiddenMatch = true;
445
+ }
584
446
  }
585
447
  const near = nameNearMisses(container, query);
586
- if (near.length > 0)
448
+ if (near.length > 0) {
587
449
  hint.nameNearMiss = near;
450
+ }
588
451
  const elsewhere = testidFoundUnder(container, query);
589
- if (elsewhere !== undefined)
452
+ if (elsewhere !== undefined) {
590
453
  hint.testidFoundUnder = elsewhere;
454
+ }
591
455
  return hint;
592
456
  }
593
457
  export function elementsMatching(query) {
@@ -1,7 +1,8 @@
1
1
  import { ElementState, SnapshotMode } from '@reticlehq/core';
2
2
  import { capturedRootOf } from './shadow-registry.js';
3
3
  import { isElement, isFrame } from './realm.js';
4
- import { getAccessibleName, getRole, getStates, getValue, isVisible } from './a11y.js';
4
+ import { getAccessibleName, getRole, getStates, getValue } from './a11y.js';
5
+ import { isVisible } from './visibility.js';
5
6
  import { refs } from './addressing/refs.js';
6
7
  import { readTestId } from './addressing/testid-attr.js';
7
8
  import { identifyComponent } from '../registry/stores/adapters.js';
@@ -0,0 +1,7 @@
1
+ export declare const HTML_DETAILS_TAG = "details";
2
+ export declare const HTML_DETAILS_OPEN_ATTRIBUTE = "open";
3
+ export declare const HTML_SUMMARY_TAG = "summary";
4
+ export declare function isHiddenByAriaOnly(el: Element): boolean;
5
+ export declare function isRenderedOutsideAriaHidden(el: Element): boolean;
6
+ export declare function isInViewport(el: Element, memo?: Map<Element, boolean>): boolean;
7
+ export declare function isVisible(el: Element, memo?: Map<Element, boolean>): boolean;