@reticlehq/browser 3.6.0 → 3.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.
Files changed (129) hide show
  1. package/dist/actions/action-report.d.ts +64 -0
  2. package/dist/actions/action-report.js +87 -0
  3. package/dist/actions/actions-dom.d.ts +5 -0
  4. package/dist/actions/actions-dom.js +32 -4
  5. package/dist/actions/actions-press.d.ts +1 -0
  6. package/dist/actions/actions-press.js +25 -1
  7. package/dist/actions/actions.d.ts +2 -37
  8. package/dist/actions/actions.js +56 -80
  9. package/dist/actions/appeared-text.js +5 -2
  10. package/dist/actions/click-target.d.ts +1 -0
  11. package/dist/actions/click-target.js +14 -0
  12. package/dist/actions/danger-context.d.ts +4 -1
  13. package/dist/actions/danger-context.js +45 -4
  14. package/dist/actions/value-input.d.ts +1 -0
  15. package/dist/actions/value-input.js +11 -0
  16. package/dist/activate.d.ts +14 -0
  17. package/dist/activate.js +43 -0
  18. package/dist/busy.d.ts +3 -0
  19. package/dist/busy.js +42 -0
  20. package/dist/commands/commands.js +49 -17
  21. package/dist/connect-options.d.ts +12 -1
  22. package/dist/connect-options.js +18 -1
  23. package/dist/dom/a11y.d.ts +2 -0
  24. package/dist/dom/a11y.js +353 -80
  25. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  26. package/dist/dom/addressing/coverage-markers.js +13 -0
  27. package/dist/dom/chart.d.ts +3 -2
  28. package/dist/dom/chart.js +3 -2
  29. package/dist/dom/dom-ignore.d.ts +8 -0
  30. package/dist/dom/dom-ignore.js +57 -2
  31. package/dist/dom/layout-audit.d.ts +15 -0
  32. package/dist/dom/layout-audit.js +208 -0
  33. package/dist/dom/paint-context.d.ts +15 -0
  34. package/dist/dom/paint-context.js +78 -0
  35. package/dist/dom/query.d.ts +2 -1
  36. package/dist/dom/query.js +163 -38
  37. package/dist/dom/render-page.d.ts +12 -0
  38. package/dist/dom/render-page.js +135 -0
  39. package/dist/dom/shadow-registry.d.ts +1 -0
  40. package/dist/dom/shadow-registry.js +27 -0
  41. package/dist/dom/snapshot.d.ts +5 -0
  42. package/dist/dom/snapshot.js +135 -36
  43. package/dist/dom/stable-selector.d.ts +1 -0
  44. package/dist/dom/stable-selector.js +19 -0
  45. package/dist/dom/text-match.d.ts +12 -0
  46. package/dist/dom/text-match.js +115 -0
  47. package/dist/dom/theme.d.ts +1 -0
  48. package/dist/dom/theme.js +21 -9
  49. package/dist/dom/virtualized.js +5 -1
  50. package/dist/index.d.ts +2 -1
  51. package/dist/index.js +5 -0
  52. package/dist/observers/blind-spots.js +3 -2
  53. package/dist/observers/canvas.d.ts +18 -0
  54. package/dist/observers/canvas.js +76 -0
  55. package/dist/observers/dom.js +96 -32
  56. package/dist/observers/field.d.ts +11 -0
  57. package/dist/observers/field.js +116 -30
  58. package/dist/observers/install-all.js +4 -0
  59. package/dist/observers/media.d.ts +2 -0
  60. package/dist/observers/media.js +32 -0
  61. package/dist/observers/net-detail/network-body.d.ts +2 -0
  62. package/dist/observers/net-detail/network-body.js +8 -1
  63. package/dist/observers/network.js +82 -13
  64. package/dist/observers/perf.js +53 -1
  65. package/dist/observers/route.d.ts +2 -0
  66. package/dist/observers/route.js +39 -9
  67. package/dist/presenter/carousel/carousel.js +2 -7
  68. package/dist/presenter/carousel/panel-slides.d.ts +2 -4
  69. package/dist/presenter/carousel/panel-slides.js +17 -15
  70. package/dist/presenter/chrome/log-memory.d.ts +1 -0
  71. package/dist/presenter/chrome/presenter-log.d.ts +7 -0
  72. package/dist/presenter/chrome/presenter-log.js +48 -8
  73. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  74. package/dist/presenter/chrome/presenter-morph.js +34 -0
  75. package/dist/presenter/chrome/presenter-report-copy.d.ts +12 -4
  76. package/dist/presenter/chrome/presenter-report-copy.js +12 -4
  77. package/dist/presenter/hud-telemetry.js +55 -4
  78. package/dist/presenter/presenter-account-styles.js +3 -3
  79. package/dist/presenter/presenter-account.d.ts +27 -5
  80. package/dist/presenter/presenter-account.js +83 -6
  81. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  82. package/dist/presenter/presenter-agent-link.js +62 -0
  83. package/dist/presenter/presenter-chat-views.d.ts +25 -4
  84. package/dist/presenter/presenter-chat-views.js +339 -72
  85. package/dist/presenter/presenter-config.d.ts +3 -0
  86. package/dist/presenter/presenter-config.js +2 -0
  87. package/dist/presenter/presenter-controls.d.ts +0 -1
  88. package/dist/presenter/presenter-controls.js +13 -91
  89. package/dist/presenter/presenter-design-tokens.js +1 -1
  90. package/dist/presenter/presenter-frame.d.ts +3 -2
  91. package/dist/presenter/presenter-frame.js +31 -24
  92. package/dist/presenter/presenter-harness-row.d.ts +56 -0
  93. package/dist/presenter/presenter-harness-row.js +170 -0
  94. package/dist/presenter/presenter-personas.d.ts +35 -0
  95. package/dist/presenter/presenter-personas.js +95 -0
  96. package/dist/presenter/presenter-plan.js +25 -24
  97. package/dist/presenter/presenter-report-styles.js +23 -22
  98. package/dist/presenter/presenter-report.js +29 -13
  99. package/dist/presenter/presenter-run-state.js +2 -1
  100. package/dist/presenter/presenter-settings-styles.js +8 -4
  101. package/dist/presenter/presenter-settings.d.ts +4 -4
  102. package/dist/presenter/presenter-settings.js +66 -28
  103. package/dist/presenter/presenter-shell-styles.js +12 -3
  104. package/dist/presenter/presenter-shell.d.ts +11 -3
  105. package/dist/presenter/presenter-shell.js +60 -22
  106. package/dist/presenter/presenter-styles.js +0 -2
  107. package/dist/presenter/presenter-workspace.js +10 -3
  108. package/dist/presenter/presenter.d.ts +3 -2
  109. package/dist/presenter/presenter.js +107 -25
  110. package/dist/registry/declared-platform.js +2 -0
  111. package/dist/registry/store-adapters.d.ts +5 -2
  112. package/dist/registry/store-adapters.js +40 -10
  113. package/dist/reticle-inject.js +771 -703
  114. package/dist/reticle.d.ts +4 -2
  115. package/dist/reticle.js +72 -8
  116. package/dist/security/page-location.d.ts +2 -0
  117. package/dist/security/page-location.js +7 -0
  118. package/dist/security/serialization.js +1 -1
  119. package/dist/storage-keys.d.ts +4 -2
  120. package/dist/storage-keys.js +8 -3
  121. package/dist/timers/native/native-timers.d.ts +1 -0
  122. package/dist/timers/native/native-timers.js +5 -0
  123. package/dist/transport/transport.d.ts +1 -0
  124. package/dist/transport/transport.js +23 -3
  125. package/dist/transport/unreachable-message.d.ts +1 -1
  126. package/dist/transport/unreachable-message.js +3 -5
  127. package/package.json +6 -2
  128. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  129. package/dist/presenter/carousel/offer-card.js +0 -56
package/dist/dom/a11y.js CHANGED
@@ -2,7 +2,8 @@ import { readTestId } from './addressing/testid-attr.js';
2
2
  import { DEFAULT_TESTID_ATTR, ElementState, REDACTED_VALUE, } from '@reticlehq/core';
3
3
  import { isButton, isHtmlElement, isImage, isInput, isMeter, isOutput, isProgress, isSelect, isTextArea, } from './realm.js';
4
4
  import { refs } from './addressing/refs.js';
5
- import { capturedRootOf } from './shadow-registry.js';
5
+ import { parentAcrossShadowBoundary } from './shadow-registry.js';
6
+ import { clippingBox, overflowAxes, scrolledInto, } from './paint-context.js';
6
7
  import { inspectChart } from './chart.js';
7
8
  import { isSensitiveKey } from '../security/serialization.js';
8
9
  import { formatSource, sourceFromDom } from './addressing/source.js';
@@ -35,36 +36,46 @@ const NAME_FROM_CONTENT = new Set([
35
36
  const INPUT_TEXT_TYPES = new Set(['text', 'email', 'tel', 'url', 'password', '']);
36
37
  function inputRole(input) {
37
38
  const type = input.type.toLowerCase();
38
- if ('search' === type)
39
+ if ('search' === type) {
39
40
  return 'searchbox';
40
- if (INPUT_TEXT_TYPES.has(type))
41
+ }
42
+ if (INPUT_TEXT_TYPES.has(type)) {
41
43
  return 'textbox';
42
- if ('checkbox' === type)
44
+ }
45
+ if ('checkbox' === type) {
43
46
  return 'checkbox';
44
- if ('radio' === type)
47
+ }
48
+ if ('radio' === type) {
45
49
  return 'radio';
46
- if ('range' === type)
50
+ }
51
+ if ('range' === type) {
47
52
  return 'slider';
48
- if ('number' === type)
53
+ }
54
+ if ('number' === type) {
49
55
  return 'spinbutton';
50
- if ('submit' === type || 'button' === type || 'reset' === type)
56
+ }
57
+ if ('submit' === type || 'button' === type || 'reset' === type) {
51
58
  return 'button';
59
+ }
52
60
  return 'textbox';
53
61
  }
54
62
  function hasAuthorNaming(el) {
55
63
  const label = el.getAttribute('aria-label');
56
- if (label !== null && label.trim().length > 0)
64
+ if (label !== null && label.trim().length > 0) {
57
65
  return true;
66
+ }
58
67
  const labelledby = el.getAttribute('aria-labelledby');
59
- if (labelledby !== null && labelledby.trim().length > 0)
68
+ if (labelledby !== null && labelledby.trim().length > 0) {
60
69
  return true;
70
+ }
61
71
  const title = el.getAttribute('title');
62
72
  return title !== null && title.trim().length > 0;
63
73
  }
64
74
  export function getRole(el) {
65
75
  const explicit = el.getAttribute('role');
66
- if (explicit !== null && explicit.trim().length > 0)
76
+ if (explicit !== null && explicit.trim().length > 0) {
67
77
  return explicit.trim();
78
+ }
68
79
  const tag = el.tagName.toLowerCase();
69
80
  switch (tag) {
70
81
  case 'a':
@@ -154,25 +165,30 @@ function collapse(text) {
154
165
  }
155
166
  function labelledByText(el) {
156
167
  const ids = el.getAttribute('aria-labelledby');
157
- if (null === ids)
168
+ if (null === ids) {
158
169
  return null;
170
+ }
159
171
  const parts = [];
160
172
  for (const id of ids.split(/\s+/)) {
161
173
  const ref = el.ownerDocument.getElementById(id);
162
- if (ref !== null)
174
+ if (ref !== null) {
163
175
  parts.push(collapse(textWithoutHidden(ref)));
176
+ }
164
177
  }
165
178
  const joined = parts.join(' ').trim();
166
179
  return joined.length > 0 ? joined : null;
167
180
  }
168
181
  function textWithoutHidden(node) {
169
- if (node.nodeType === Node.TEXT_NODE)
182
+ if (node.nodeType === Node.TEXT_NODE) {
170
183
  return node.textContent ?? '';
171
- if (node.nodeType !== Node.ELEMENT_NODE)
184
+ }
185
+ if (node.nodeType !== Node.ELEMENT_NODE) {
172
186
  return '';
187
+ }
173
188
  const el = node;
174
- if ('true' === el.getAttribute('aria-hidden'))
189
+ if ('true' === el.getAttribute('aria-hidden')) {
175
190
  return '';
191
+ }
176
192
  if (isImage(el)) {
177
193
  const alt = el.getAttribute('alt');
178
194
  return null === alt ? '' : alt;
@@ -181,11 +197,13 @@ function textWithoutHidden(node) {
181
197
  let lastWasElement = false;
182
198
  for (const child of el.childNodes) {
183
199
  const piece = textWithoutHidden(child);
184
- if (0 === piece.length)
200
+ if (0 === piece.length) {
185
201
  continue;
202
+ }
186
203
  const isEl = Node.ELEMENT_NODE === child.nodeType;
187
- if (result.length > 0 && (isEl || lastWasElement))
204
+ if (result.length > 0 && (isEl || lastWasElement)) {
188
205
  result += ' ';
206
+ }
189
207
  result += piece;
190
208
  lastWasElement = isEl;
191
209
  }
@@ -193,15 +211,18 @@ function textWithoutHidden(node) {
193
211
  }
194
212
  export function getAccessibleName(el) {
195
213
  const labelled = labelledByText(el);
196
- if (labelled !== null)
214
+ if (labelled !== null) {
197
215
  return labelled;
216
+ }
198
217
  const ariaLabel = el.getAttribute('aria-label');
199
- if (ariaLabel !== null && ariaLabel.trim().length > 0)
218
+ if (ariaLabel !== null && ariaLabel.trim().length > 0) {
200
219
  return ariaLabel.trim();
220
+ }
201
221
  if (isImage(el)) {
202
222
  const alt = el.getAttribute('alt');
203
- if (alt !== null)
223
+ if (alt !== null) {
204
224
  return alt.trim();
225
+ }
205
226
  }
206
227
  const isLabelable = isInput(el) ||
207
228
  isTextArea(el) ||
@@ -217,16 +238,36 @@ export function getAccessibleName(el) {
217
238
  .map((l) => collapse(textWithoutHidden(l)))
218
239
  .join(' ')
219
240
  .trim();
220
- if (text.length > 0)
241
+ if (text.length > 0) {
221
242
  return text;
243
+ }
222
244
  }
223
245
  }
224
246
  if ('fieldset' === el.tagName.toLowerCase()) {
225
247
  const legend = [...el.children].find((child) => 'legend' === child.tagName.toLowerCase());
226
248
  if (legend !== undefined) {
227
249
  const text = collapse(textWithoutHidden(legend));
228
- if (text.length > 0)
250
+ if (text.length > 0) {
229
251
  return text;
252
+ }
253
+ }
254
+ }
255
+ if ('table' === el.tagName.toLowerCase()) {
256
+ const caption = [...el.children].find((child) => 'caption' === child.tagName.toLowerCase());
257
+ if (caption !== undefined) {
258
+ const text = collapse(textWithoutHidden(caption));
259
+ if (text.length > 0) {
260
+ return text;
261
+ }
262
+ }
263
+ }
264
+ if ('figure' === el.tagName.toLowerCase()) {
265
+ const figcaption = [...el.children].find((child) => 'figcaption' === child.tagName.toLowerCase());
266
+ if (figcaption !== undefined) {
267
+ const text = collapse(textWithoutHidden(figcaption));
268
+ if (text.length > 0) {
269
+ return text;
270
+ }
230
271
  }
231
272
  }
232
273
  if (isInput(el) || isTextArea(el) || isSelect(el)) {
@@ -234,49 +275,68 @@ export function getAccessibleName(el) {
234
275
  const type = el.type.toLowerCase();
235
276
  if ('submit' === type || 'button' === type || 'reset' === type) {
236
277
  const value = collapse(el.value);
237
- if (value.length > 0)
278
+ if (value.length > 0) {
238
279
  return value;
280
+ }
239
281
  }
240
282
  }
241
283
  if (isInput(el) || isTextArea(el)) {
242
284
  const placeholder = el.getAttribute('placeholder');
243
- if (placeholder !== null && placeholder.trim().length > 0)
285
+ if (placeholder !== null && placeholder.trim().length > 0) {
244
286
  return placeholder.trim();
287
+ }
245
288
  }
246
289
  }
247
290
  if (NAME_FROM_CONTENT.has(getRole(el))) {
248
291
  const text = collapse(textWithoutHidden(el));
249
- if (text.length > 0)
292
+ if (text.length > 0) {
250
293
  return text;
294
+ }
251
295
  }
252
296
  const title = el.getAttribute('title');
253
- if (title !== null && title.trim().length > 0)
297
+ if (title !== null && title.trim().length > 0) {
254
298
  return title.trim();
299
+ }
255
300
  return '';
256
301
  }
257
302
  function ariaBool(el, attr) {
258
303
  const value = el.getAttribute(attr);
259
- if (null === value)
304
+ if (null === value) {
260
305
  return undefined;
306
+ }
261
307
  return 'true' === value;
262
308
  }
263
309
  export function getStates(el, visible = isVisible(el)) {
264
310
  const states = [ElementState.PRESENT];
265
311
  states.push(visible ? ElementState.VISIBLE : ElementState.HIDDEN);
266
- const disabledProp = (isButton(el) || isInput(el) || isSelect(el) || isTextArea(el)) && el.disabled;
267
- const disabled = disabledProp || true === ariaBool(el, 'aria-disabled');
312
+ const disabled = el.matches(':disabled') || true === ariaBool(el, 'aria-disabled');
268
313
  states.push(disabled ? ElementState.DISABLED : ElementState.ENABLED);
269
314
  const checkedProp = isInput(el) && ('checkbox' === el.type || 'radio' === el.type) && el.checked;
270
- if (checkedProp || true === ariaBool(el, 'aria-checked'))
315
+ if (checkedProp || true === ariaBool(el, 'aria-checked')) {
271
316
  states.push(ElementState.CHECKED);
272
- if (true === ariaBool(el, 'aria-expanded'))
317
+ }
318
+ if (true === ariaBool(el, 'aria-expanded') || isOpenDetailsSummary(el)) {
273
319
  states.push(ElementState.EXPANDED);
274
- if (true === ariaBool(el, 'aria-pressed'))
320
+ }
321
+ if (true === ariaBool(el, 'aria-pressed')) {
275
322
  states.push(ElementState.PRESSED);
276
- if (el.ownerDocument.activeElement === el)
323
+ }
324
+ if (el.ownerDocument.activeElement === el) {
277
325
  states.push(ElementState.FOCUSED);
326
+ }
278
327
  return states;
279
328
  }
329
+ function isOpenDetailsSummary(el) {
330
+ const details = el.parentElement;
331
+ if (HTML_SUMMARY_TAG !== el.localName ||
332
+ null === details ||
333
+ HTML_DETAILS_TAG !== details.localName) {
334
+ return false;
335
+ }
336
+ const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
337
+ return summary === el && details.hasAttribute(HTML_DETAILS_OPEN_ATTRIBUTE);
338
+ }
339
+ const SENSITIVE_AUTOCOMPLETE = /\b(cc-|current-password|new-password|one-time-code)/i;
280
340
  export function isSensitiveField(el) {
281
341
  if (!isInput(el) && !isTextArea(el) && !isSelect(el)) {
282
342
  return false;
@@ -289,15 +349,16 @@ export function isSensitiveField(el) {
289
349
  el.getAttribute(DEFAULT_TESTID_ATTR) ?? '',
290
350
  el.getAttribute('aria-label') ?? '',
291
351
  ];
292
- const sensitiveAutocomplete = /current-password|new-password|cc-number|cc-csc|one-time-code/i.test(autocomplete);
352
+ const sensitiveAutocomplete = SENSITIVE_AUTOCOMPLETE.test(autocomplete);
293
353
  return ((isInput(el) && 'password' === el.type.toLowerCase()) ||
294
354
  sensitiveAutocomplete ||
295
355
  identifiers.some(isSensitiveKey));
296
356
  }
297
357
  export function getValue(el) {
298
358
  if (isInput(el) || isTextArea(el) || isSelect(el)) {
299
- if (isSensitiveField(el))
300
- return REDACTED_VALUE;
359
+ if (isSensitiveField(el)) {
360
+ return '' === el.value ? '' : REDACTED_VALUE;
361
+ }
301
362
  return el.value;
302
363
  }
303
364
  const valueNow = el.getAttribute('aria-valuenow');
@@ -305,82 +366,290 @@ export function getValue(el) {
305
366
  }
306
367
  function hiddenInsideClosedDetails(el) {
307
368
  const parent = el.parentElement;
308
- if (null === parent)
369
+ if (null === parent) {
309
370
  return false;
371
+ }
310
372
  const details = parent.closest(HTML_DETAILS_TAG);
311
- if (null === details || details.hasAttribute(HTML_DETAILS_OPEN_ATTRIBUTE))
373
+ if (null === details || details.hasAttribute(HTML_DETAILS_OPEN_ATTRIBUTE)) {
312
374
  return false;
375
+ }
313
376
  const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
314
377
  return summary === undefined || !summary.contains(el);
315
378
  }
316
379
  function stalledFade(el) {
317
- if (!el.ownerDocument.hidden || !('getAnimations' in el))
380
+ if (!el.ownerDocument.hidden || !('getAnimations' in el)) {
318
381
  return false;
382
+ }
319
383
  return el.getAnimations().some((a) => 'running' === a.playState || a.pending);
320
384
  }
321
- function selfHidden(el) {
322
- if ('true' === el.getAttribute('aria-hidden'))
323
- return true;
324
- if (isHtmlElement(el) && el.hidden)
385
+ const OVERLAY_ARIA_HIDDEN = 'data-aria-hidden';
386
+ function hiddenByAnOverlay(el) {
387
+ return el.hasAttribute(OVERLAY_ARIA_HIDDEN);
388
+ }
389
+ function selfCssHidden(el, style) {
390
+ if (isHtmlElement(el) && el.hidden) {
325
391
  return true;
326
- if (hiddenInsideClosedDetails(el))
392
+ }
393
+ if (hiddenInsideClosedDetails(el)) {
327
394
  return true;
328
- const view = el.ownerDocument.defaultView;
329
- if (view !== null) {
330
- const style = view.getComputedStyle(el);
395
+ }
396
+ if (style !== null) {
331
397
  if ('none' === style.display ||
332
398
  'hidden' === style.visibility ||
333
399
  'collapse' === style.visibility) {
334
400
  return true;
335
401
  }
336
- if (0 === Number.parseFloat(style.opacity || '1') && !stalledFade(el))
402
+ if (0 === Number.parseFloat(style.opacity || '1') && !stalledFade(el)) {
337
403
  return true;
404
+ }
338
405
  }
339
406
  return false;
340
407
  }
341
- function slotInCapturedClosedRoot(el) {
342
- const host = el.parentElement;
343
- if (null === host || null !== host.shadowRoot)
344
- return null;
345
- const root = capturedRootOf(host);
346
- if (null === root)
347
- return null;
348
- for (const slot of Array.from(root.querySelectorAll('slot'))) {
349
- if (slot.assignedElements().includes(el))
350
- return slot;
408
+ function selfHidden(el, style) {
409
+ if ('true' === el.getAttribute('aria-hidden') && !hiddenByAnOverlay(el)) {
410
+ return true;
411
+ }
412
+ return selfCssHidden(el, style);
413
+ }
414
+ export function isHiddenByAriaOnly(el) {
415
+ let foundAriaHidden = false;
416
+ let current = el;
417
+ while (null !== current) {
418
+ if ('true' === current.getAttribute('aria-hidden')) {
419
+ foundAriaHidden = true;
420
+ }
421
+ const style = current.ownerDocument.defaultView?.getComputedStyle(current) ?? null;
422
+ if (selfCssHidden(current, style)) {
423
+ return false;
424
+ }
425
+ current = parentAcrossShadowBoundary(current);
426
+ }
427
+ return foundAriaHidden;
428
+ }
429
+ export function isRenderedOutsideAriaHidden(el) {
430
+ let current = el;
431
+ while (null !== current) {
432
+ if ('true' === current.getAttribute('aria-hidden')) {
433
+ return false;
434
+ }
435
+ const style = current.ownerDocument.defaultView?.getComputedStyle(current) ?? null;
436
+ if (selfCssHidden(current, style)) {
437
+ return false;
438
+ }
439
+ current = parentAcrossShadowBoundary(current);
440
+ }
441
+ return true;
442
+ }
443
+ const CLIPPING_OVERFLOW = new Set(['hidden', 'clip']);
444
+ const SCROLLING_OVERFLOW = new Set(['auto', 'scroll']);
445
+ const NON_CLIPPING_DISPLAY = new Set([
446
+ 'contents',
447
+ 'table-row',
448
+ 'table-row-group',
449
+ 'table-header-group',
450
+ 'table-footer-group',
451
+ 'table-column',
452
+ 'table-column-group',
453
+ ]);
454
+ const TRANSFORM_CONTAINING_BLOCK_PROPERTIES = new Set([
455
+ 'transform',
456
+ 'translate',
457
+ 'rotate',
458
+ 'scale',
459
+ 'perspective',
460
+ ]);
461
+ const CONTAINING_BLOCK_PROPERTIES = new Set([
462
+ ...TRANSFORM_CONTAINING_BLOCK_PROPERTIES,
463
+ 'filter',
464
+ 'backdrop-filter',
465
+ ]);
466
+ const CONTAINING_BLOCK_CONTAIN = new Set(['layout', 'paint', 'strict', 'content']);
467
+ const REPLACED_INLINE_TAGS = new Set([
468
+ 'img',
469
+ 'video',
470
+ 'audio',
471
+ 'canvas',
472
+ 'iframe',
473
+ 'embed',
474
+ 'object',
475
+ 'input',
476
+ 'textarea',
477
+ 'select',
478
+ ]);
479
+ const visibilityInfoMemos = new WeakMap();
480
+ function visibilityInfoMemo(memo) {
481
+ if (memo === undefined) {
482
+ return new Map();
483
+ }
484
+ let info = visibilityInfoMemos.get(memo);
485
+ if (info === undefined) {
486
+ info = new Map();
487
+ visibilityInfoMemos.set(memo, info);
488
+ }
489
+ return info;
490
+ }
491
+ function ownVisibilityInfo(el, memo) {
492
+ let info = memo.get(el);
493
+ if (info === undefined) {
494
+ const style = el.ownerDocument.defaultView?.getComputedStyle(el) ?? null;
495
+ if (selfHidden(el, style)) {
496
+ info = {
497
+ hidden: true,
498
+ style,
499
+ parent: null,
500
+ clipX: false,
501
+ clipY: false,
502
+ scrollX: false,
503
+ scrollY: false,
504
+ hasClipping: false,
505
+ };
506
+ memo.set(el, info);
507
+ return info;
508
+ }
509
+ const parent = parentAcrossShadowBoundary(el);
510
+ const inherited = null === parent ? undefined : ownVisibilityInfo(parent, memo);
511
+ const [overflowX, overflowY] = overflowAxes(style);
512
+ const root = el.ownerDocument.documentElement;
513
+ const viewportOverflow = el === root ||
514
+ (el === el.ownerDocument.body &&
515
+ overflowAxes(ownVisibilityInfo(root, memo).style).every((axis) => 'visible' === axis));
516
+ const hasClipBox = !viewportOverflow &&
517
+ !NON_CLIPPING_DISPLAY.has(style?.display ?? '') &&
518
+ !(isHtmlElement(el) && 'inline' === style?.display);
519
+ const clipX = hasClipBox && CLIPPING_OVERFLOW.has(overflowX);
520
+ const clipY = hasClipBox && CLIPPING_OVERFLOW.has(overflowY);
521
+ info = {
522
+ hidden: true === inherited?.hidden,
523
+ style,
524
+ parent,
525
+ clipX,
526
+ clipY,
527
+ scrollX: hasClipBox && SCROLLING_OVERFLOW.has(overflowX),
528
+ scrollY: hasClipBox && SCROLLING_OVERFLOW.has(overflowY),
529
+ hasClipping: clipX || clipY || true === inherited?.hasClipping,
530
+ };
531
+ memo.set(el, info);
532
+ }
533
+ return info;
534
+ }
535
+ function positionedContainingBlock(el, fixed, memo) {
536
+ for (let parent = ownVisibilityInfo(el, memo).parent; parent !== null; parent = ownVisibilityInfo(parent, memo).parent) {
537
+ const style = ownVisibilityInfo(parent, memo).style;
538
+ if (null === style || 'contents' === style.display) {
539
+ continue;
540
+ }
541
+ if (!fixed && '' !== style.position && 'static' !== style.position) {
542
+ return parent;
543
+ }
544
+ const transformable = !('inline' === style.display &&
545
+ isHtmlElement(parent) &&
546
+ !REPLACED_INLINE_TAGS.has(parent.localName)) &&
547
+ 'table-column' !== style.display &&
548
+ 'table-column-group' !== style.display;
549
+ const applies = (property) => transformable || !TRANSFORM_CONTAINING_BLOCK_PROPERTIES.has(property);
550
+ if ([...CONTAINING_BLOCK_PROPERTIES].some((property) => {
551
+ const value = style.getPropertyValue(property);
552
+ return applies(property) && '' !== value && 'none' !== value;
553
+ }) ||
554
+ style.willChange
555
+ .split(',')
556
+ .some((property) => CONTAINING_BLOCK_PROPERTIES.has(property.trim()) && applies(property.trim())) ||
557
+ (transformable &&
558
+ !NON_CLIPPING_DISPLAY.has(style.display) &&
559
+ (style.contain.split(/\s+/).some((value) => CONTAINING_BLOCK_CONTAIN.has(value)) ||
560
+ 'auto' === style.contentVisibility))) {
561
+ return parent;
562
+ }
351
563
  }
352
564
  return null;
353
565
  }
354
- function parentAcrossShadowBoundary(el) {
355
- const slot = el.assignedSlot ?? slotInCapturedClosedRoot(el);
356
- if (null !== slot)
357
- return slot;
358
- if (null !== el.parentElement)
359
- return el.parentElement;
360
- const host = el.getRootNode().host;
361
- return host ?? null;
566
+ function visibleBounds(el, memo, initial, throughScrollers = false) {
567
+ let bounds = initial;
568
+ const target = ownVisibilityInfo(el, memo);
569
+ if (target.hidden) {
570
+ return null;
571
+ }
572
+ if (null === target.parent || !ownVisibilityInfo(target.parent, memo).hasClipping) {
573
+ return bounds;
574
+ }
575
+ let clipFrom;
576
+ for (let current = el; current !== null; current = ownVisibilityInfo(current, memo).parent) {
577
+ const info = ownVisibilityInfo(current, memo);
578
+ if (info.hidden) {
579
+ return null;
580
+ }
581
+ if (current === clipFrom) {
582
+ clipFrom = undefined;
583
+ }
584
+ const style = info.style;
585
+ if (throughScrollers &&
586
+ current !== el &&
587
+ undefined === clipFrom &&
588
+ (info.scrollX || info.scrollY)) {
589
+ bounds ??= el.getBoundingClientRect();
590
+ if (bounds.width > 0 && bounds.height > 0) {
591
+ bounds = scrolledInto(bounds, clippingBox(current, info), info.scrollX, info.scrollY);
592
+ }
593
+ }
594
+ if (current !== el && undefined === clipFrom && (info.clipX || info.clipY)) {
595
+ bounds ??= el.getBoundingClientRect();
596
+ if (bounds.width > 0 && bounds.height > 0) {
597
+ const clip = clippingBox(current, info);
598
+ const left = info.clipX ? Math.max(bounds.left, clip.left) : bounds.left;
599
+ const right = info.clipX ? Math.min(bounds.right, clip.right) : bounds.right;
600
+ const top = info.clipY ? Math.max(bounds.top, clip.top) : bounds.top;
601
+ const bottom = info.clipY ? Math.min(bounds.bottom, clip.bottom) : bounds.bottom;
602
+ if (right <= left || bottom <= top) {
603
+ return null;
604
+ }
605
+ bounds = { left, right, top, bottom, width: right - left, height: bottom - top };
606
+ }
607
+ }
608
+ if (clipFrom === undefined && ('absolute' === style?.position || 'fixed' === style?.position)) {
609
+ clipFrom = positionedContainingBlock(current, 'fixed' === style?.position, memo);
610
+ }
611
+ }
612
+ return bounds;
362
613
  }
363
614
  export function isInViewport(el, memo) {
364
- if (!isVisible(el, memo))
615
+ if (!isVisible(el, memo)) {
365
616
  return false;
617
+ }
366
618
  const view = el.ownerDocument.defaultView;
367
- if (null === view)
619
+ if (null === view) {
620
+ return false;
621
+ }
622
+ const r = visibleBounds(el, visibilityInfoMemo(memo), el.getBoundingClientRect());
623
+ if (null === r || undefined === r) {
368
624
  return false;
369
- const r = el.getBoundingClientRect();
370
- if (r.width <= 0 || r.height <= 0)
625
+ }
626
+ if (r.width <= 0 || r.height <= 0) {
371
627
  return false;
628
+ }
372
629
  return r.bottom > 0 && r.right > 0 && r.top < view.innerHeight && r.left < view.innerWidth;
373
630
  }
631
+ function collapsed(el, memo) {
632
+ const own = ownVisibilityInfo(el, memo);
633
+ if (!own.clipX && !own.clipY) {
634
+ return false;
635
+ }
636
+ const rects = Array.from(el.getClientRects());
637
+ return (rects.length > 0 &&
638
+ rects.every((r) => (own.clipX && 0 === r.width) || (own.clipY && 0 === r.height)));
639
+ }
374
640
  export function isVisible(el, memo) {
375
- if (!el.isConnected)
641
+ if (!el.isConnected) {
376
642
  return false;
643
+ }
377
644
  const cached = memo?.get(el);
378
- if (cached !== undefined)
645
+ if (cached !== undefined) {
379
646
  return cached;
380
- const parent = parentAcrossShadowBoundary(el);
381
- const result = !selfHidden(el) && (null === parent || isVisible(parent, memo));
382
- if (memo !== undefined)
647
+ }
648
+ const info = visibilityInfoMemo(memo);
649
+ const result = visibleBounds(el, info, undefined, true) !== null && !collapsed(el, info);
650
+ if (memo !== undefined) {
383
651
  memo.set(el, result);
652
+ }
384
653
  return result;
385
654
  }
386
655
  const MAX_TEXT = 80;
@@ -400,17 +669,21 @@ export function describe(el, memo) {
400
669
  states: getStates(el, visible),
401
670
  visible,
402
671
  };
403
- if (value !== undefined && value.length > 0)
672
+ if (value !== undefined && value.length > 0) {
404
673
  base.value = value;
405
- if (text.length > 0 && text !== name)
674
+ }
675
+ if (text.length > 0 && text !== name) {
406
676
  base.text = text;
677
+ }
407
678
  const source = formatSource(sourceFromDom(el));
408
- if (source !== undefined)
679
+ if (source !== undefined) {
409
680
  base.source = source;
681
+ }
410
682
  if (el.querySelector('path, polyline, polygon') !== null) {
411
683
  const faults = inspectChart(el).findings;
412
- if (faults.length > 0)
684
+ if (faults.length > 0) {
413
685
  base.chart = faults;
686
+ }
414
687
  }
415
688
  return base;
416
689
  }
@@ -0,0 +1,6 @@
1
+ export declare function coverageMarkers(doc: Document): string[];
2
+ export declare function notApplicableFor(page: {
3
+ buildPlugin: boolean;
4
+ adapters: readonly string[];
5
+ markers: readonly string[];
6
+ }): string[];
@@ -0,0 +1,13 @@
1
+ import { CoverageMarker, SOURCE_MAPPING_CAPABILITY, DATA_RETICLE_SOURCE_ATTR, RETICLE_OVERLAY_SELECTOR, } from '@reticlehq/core';
2
+ import { testIdSelector } from './testid-attr.js';
3
+ const appCarries = (doc, selector) => Array.from(doc.querySelectorAll(selector)).some((el) => null === el.closest(RETICLE_OVERLAY_SELECTOR));
4
+ export function coverageMarkers(doc) {
5
+ return [
6
+ ...(appCarries(doc, `[${DATA_RETICLE_SOURCE_ATTR}]`) ? [CoverageMarker.SOURCE] : []),
7
+ ...(appCarries(doc, testIdSelector()) ? [CoverageMarker.TESTID] : []),
8
+ ];
9
+ }
10
+ export function notApplicableFor(page) {
11
+ const couldStamp = page.buildPlugin || 0 < page.adapters.length || page.markers.includes(CoverageMarker.SOURCE);
12
+ return couldStamp ? [] : [SOURCE_MAPPING_CAPABILITY];
13
+ }
@@ -17,7 +17,7 @@ export interface CanvasChartData {
17
17
  library: 'chartjs' | 'echarts';
18
18
  data: unknown;
19
19
  }
20
- export declare function canvasChartData(canvas: Element, globals: {
20
+ export interface ChartGlobals {
21
21
  Chart?: {
22
22
  getChart?: (el: Element) => {
23
23
  data?: unknown;
@@ -28,4 +28,5 @@ export declare function canvasChartData(canvas: Element, globals: {
28
28
  getOption?: () => unknown;
29
29
  } | undefined;
30
30
  };
31
- }): CanvasChartData | null;
31
+ }
32
+ export declare function canvasChartData(canvas: Element, globals: ChartGlobals | Window): CanvasChartData | null;