@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/chart.js CHANGED
@@ -46,11 +46,12 @@ export function inspectChart(root) {
46
46
  return { examined, findings, canvas };
47
47
  }
48
48
  export function canvasChartData(canvas, globals) {
49
- const chartjs = globals.Chart?.getChart?.(canvas);
49
+ const libs = globals;
50
+ const chartjs = libs.Chart?.getChart?.(canvas);
50
51
  if (chartjs !== undefined && chartjs !== null) {
51
52
  return { library: 'chartjs', data: chartjs.data ?? null };
52
53
  }
53
- const echart = globals.echarts?.getInstanceByDom?.(canvas);
54
+ const echart = libs.echarts?.getInstanceByDom?.(canvas);
54
55
  if (echart !== undefined && echart !== null) {
55
56
  return { library: 'echarts', data: echart.getOption?.() ?? null };
56
57
  }
@@ -1,8 +1,16 @@
1
1
  export declare const RETICLE_OVERLAY: string;
2
2
  export declare function setPresenterVisible(visible: boolean): void;
3
+ export declare function setPresenterViewer(harness: boolean): void;
3
4
  export declare function isPresenterVisible(): boolean;
4
5
  export declare function setIgnoreSelectors(selectors: string[]): void;
5
6
  export declare function isReticleOverlay(el: Element): boolean;
7
+ export declare function isUnobserved(el: Element): boolean;
6
8
  export declare function isReticleUi(node: Element | null): boolean;
7
9
  export declare function isIgnored(el: Element): boolean;
10
+ export declare function isWordsIgnored(el: Element): boolean;
11
+ export declare const FrameworkErrorDialog: {
12
+ readonly NEXT: "Next.js error dialog";
13
+ readonly VITE: "Vite error overlay";
14
+ };
15
+ export declare function dialogCovering(matches: readonly Element[]): string | undefined;
8
16
  export declare function appModalOpen(doc: Document): boolean;
@@ -1,10 +1,17 @@
1
- export const RETICLE_OVERLAY = '[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark],[data-reticle-blocker],[data-reticle-tour]';
1
+ import { RETICLE_OVERLAY_SELECTOR } from '@reticlehq/core';
2
+ import { capturedRootOf } from './shadow-registry.js';
3
+ export const RETICLE_OVERLAY = RETICLE_OVERLAY_SELECTOR;
2
4
  const DEV_OVERLAYS = '[data-agentation],#__next-build-watcher,nextjs-portal,[data-nextjs-dialog],[data-nextjs-toast]';
3
5
  let extraIgnore = '';
4
6
  let presenterVisible = false;
5
7
  export function setPresenterVisible(visible) {
6
8
  presenterVisible = visible;
7
9
  }
10
+ let harnessViewing = false;
11
+ export function setPresenterViewer(harness) {
12
+ harnessViewing = harness;
13
+ }
14
+ const exposed = () => presenterVisible && !harnessViewing;
8
15
  export function isPresenterVisible() {
9
16
  return presenterVisible;
10
17
  }
@@ -14,14 +21,62 @@ export function setIgnoreSelectors(selectors) {
14
21
  export function isReticleOverlay(el) {
15
22
  return el.closest(RETICLE_OVERLAY) !== null;
16
23
  }
24
+ const HUD_DOCK = '[data-reticle-dock]';
25
+ export function isUnobserved(el) {
26
+ if (!isReticleOverlay(el))
27
+ return false;
28
+ return !exposed() || null === el.closest(HUD_DOCK);
29
+ }
17
30
  export function isReticleUi(node) {
18
31
  return node !== null && node.closest(RETICLE_OVERLAY) !== null;
19
32
  }
33
+ const HUD_NARRATION = '[data-reticle-log],[data-reticle-harness-log],[data-reticle-chat-pill-text],[data-reticle-agent-said],[data-reticle-agent-note],[data-reticle-act-line]';
20
34
  export function isIgnored(el) {
21
- const ours = presenterVisible ? '' : RETICLE_OVERLAY;
35
+ const ours = exposed() ? HUD_NARRATION : RETICLE_OVERLAY;
22
36
  const sel = [ours, DEV_OVERLAYS, extraIgnore].filter((part) => part.length > 0).join(',');
23
37
  return el.closest(sel) !== null;
24
38
  }
39
+ export function isWordsIgnored(el) {
40
+ return isIgnored(el) || isReticleOverlay(el);
41
+ }
42
+ export const FrameworkErrorDialog = {
43
+ NEXT: 'Next.js error dialog',
44
+ VITE: 'Vite error overlay',
45
+ };
46
+ const NEXT_PORTAL = 'nextjs-portal';
47
+ const NEXT_DIALOG = '[data-nextjs-dialog]';
48
+ const VITE_OVERLAY = 'vite-error-overlay';
49
+ function openFrameworkErrorDialog(doc) {
50
+ const vite = doc.querySelector(VITE_OVERLAY);
51
+ if (vite !== null)
52
+ return { name: FrameworkErrorDialog.VITE, host: vite };
53
+ for (const portal of Array.from(doc.querySelectorAll(NEXT_PORTAL))) {
54
+ const root = portal.shadowRoot ?? capturedRootOf(portal);
55
+ if (null !== (root ?? portal).querySelector(NEXT_DIALOG)) {
56
+ return { name: FrameworkErrorDialog.NEXT, host: portal };
57
+ }
58
+ }
59
+ const light = doc.querySelector(NEXT_DIALOG);
60
+ return null === light ? undefined : { name: FrameworkErrorDialog.NEXT, host: light };
61
+ }
62
+ export function dialogCovering(matches) {
63
+ const first = matches[0];
64
+ if (first === undefined)
65
+ return undefined;
66
+ const dialog = openFrameworkErrorDialog(first.ownerDocument);
67
+ if (dialog === undefined)
68
+ return undefined;
69
+ return matches.some((el) => !isInsideHost(el, dialog.host)) ? dialog.name : undefined;
70
+ }
71
+ function isInsideHost(el, host) {
72
+ for (let node = el; node !== null;) {
73
+ if (node === host)
74
+ return true;
75
+ const root = node.getRootNode();
76
+ node = node.parentNode ?? (root instanceof ShadowRoot ? root.host : null);
77
+ }
78
+ return false;
79
+ }
25
80
  const APP_MODAL = 'dialog[open], [aria-modal="true"]';
26
81
  export function appModalOpen(doc) {
27
82
  for (const el of Array.from(doc.querySelectorAll(APP_MODAL))) {
@@ -0,0 +1,15 @@
1
+ export declare const LayoutDefect: {
2
+ readonly CLIPPED: "clipped";
3
+ readonly OVERLAP: "overlap";
4
+ readonly CONTRAST: "contrast";
5
+ };
6
+ export type LayoutDefect = (typeof LayoutDefect)[keyof typeof LayoutDefect];
7
+ export interface LayoutFinding {
8
+ kind: LayoutDefect;
9
+ ref: string;
10
+ what: string;
11
+ detail: string;
12
+ value: number;
13
+ bound: number;
14
+ }
15
+ export declare function auditLayout(root?: Element): LayoutFinding[];
@@ -0,0 +1,208 @@
1
+ import { refs } from './addressing/refs.js';
2
+ import { getAccessibleName } from './a11y.js';
3
+ import { isIgnored } from './dom-ignore.js';
4
+ export const LayoutDefect = {
5
+ CLIPPED: 'clipped',
6
+ OVERLAP: 'overlap',
7
+ CONTRAST: 'contrast',
8
+ };
9
+ const MAX_FINDINGS = 20;
10
+ const MAX_CONTROLS = 300;
11
+ const WHAT_CHARS = 60;
12
+ const SLACK_PX = 1;
13
+ const OVERLAP_SHARE = 0.25;
14
+ const MIN_CONTRAST = 4.5;
15
+ const MIN_CONTRAST_LARGE = 3;
16
+ const LARGE_PX = 24;
17
+ const LARGE_BOLD_PX = 18.66;
18
+ const BOLD_WEIGHT = 700;
19
+ const CONTROLS = 'button, a[href], input:not([type="hidden"]), select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"]';
20
+ const CLIPPED_AWAY = /rect\(\s*0(px)?[\s,]+0(px)?[\s,]+0(px)?[\s,]+0(px)?\s*\)|inset\(\s*50%/;
21
+ const MIN_BOX_PX = 4;
22
+ const shown = (el) => {
23
+ const rect = el.getBoundingClientRect();
24
+ if (rect.width < MIN_BOX_PX || rect.height < MIN_BOX_PX) {
25
+ return null;
26
+ }
27
+ const style = getComputedStyle(el);
28
+ if ('hidden' === style.visibility || 'none' === style.display || '0' === style.opacity) {
29
+ return null;
30
+ }
31
+ if (CLIPPED_AWAY.test(style.clip) || CLIPPED_AWAY.test(style.clipPath)) {
32
+ return null;
33
+ }
34
+ return rect;
35
+ };
36
+ const whatOf = (el) => (getAccessibleName(el) || (el.textContent ?? ''))
37
+ .replace(/\s+/g, ' ')
38
+ .trim()
39
+ .slice(0, WHAT_CHARS);
40
+ const ownText = (el) => [...el.childNodes]
41
+ .filter((n) => Node.TEXT_NODE === n.nodeType)
42
+ .map((n) => n.textContent ?? '')
43
+ .join('')
44
+ .trim();
45
+ const CUTS = new Set(['hidden', 'clip']);
46
+ const clipped = (el) => {
47
+ const style = getComputedStyle(el);
48
+ const wide = el.scrollWidth > el.clientWidth + SLACK_PX && CUTS.has(style.overflowX);
49
+ const tall = el.scrollHeight > el.clientHeight + SLACK_PX && CUTS.has(style.overflowY);
50
+ const declared = 'ellipsis' === style.textOverflow ||
51
+ ('' !== style.getPropertyValue('-webkit-line-clamp') &&
52
+ 'none' !== style.getPropertyValue('-webkit-line-clamp'));
53
+ if (declared || (!wide && !tall)) {
54
+ return null;
55
+ }
56
+ return wide
57
+ ? {
58
+ detail: `${String(el.scrollWidth)}px of text in a ${String(el.clientWidth)}px box, cut with no ellipsis`,
59
+ value: el.scrollWidth,
60
+ bound: el.clientWidth,
61
+ }
62
+ : {
63
+ detail: `${String(el.scrollHeight)}px of text in a ${String(el.clientHeight)}px box, cut with no line clamp`,
64
+ value: el.scrollHeight,
65
+ bound: el.clientHeight,
66
+ };
67
+ };
68
+ const RGB = /rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)/;
69
+ const parse = (value) => {
70
+ const m = RGB.exec(value);
71
+ if (null === m) {
72
+ return null;
73
+ }
74
+ const alphaRaw = m[4];
75
+ let alpha = 1;
76
+ if (alphaRaw !== undefined) {
77
+ alpha = alphaRaw.endsWith('%') ? Number(alphaRaw.slice(0, -1)) / 100 : Number(alphaRaw);
78
+ }
79
+ return [Number(m[1]), Number(m[2]), Number(m[3]), alpha];
80
+ };
81
+ const luminance = ([r, g, b]) => {
82
+ const lin = (c) => {
83
+ const s = c / 255;
84
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
85
+ };
86
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
87
+ };
88
+ const over = (top, under) => {
89
+ const a = top[3];
90
+ return [
91
+ top[0] * a + under[0] * (1 - a),
92
+ top[1] * a + under[1] * (1 - a),
93
+ top[2] * a + under[2] * (1 - a),
94
+ 1,
95
+ ];
96
+ };
97
+ const backdrop = (el) => {
98
+ const layers = [];
99
+ for (let at = el; null !== at; at = at.parentElement) {
100
+ const style = getComputedStyle(at);
101
+ if ('none' !== style.backgroundImage && '' !== style.backgroundImage) {
102
+ return null;
103
+ }
104
+ const colour = parse(style.backgroundColor);
105
+ if (null !== colour && colour[3] > 0) {
106
+ layers.push(colour);
107
+ if (1 === colour[3]) {
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ return layers.reduceRight((under, top) => over(top, under), [255, 255, 255, 1]);
113
+ };
114
+ const contrastOf = (el) => {
115
+ const style = getComputedStyle(el);
116
+ const fg = parse(style.color);
117
+ const bg = backdrop(el);
118
+ if (null === fg || null === bg) {
119
+ return null;
120
+ }
121
+ const text = over(fg, bg);
122
+ const [hi, lo] = [luminance(text), luminance(bg)].sort((a, b) => b - a);
123
+ const size = Number.parseFloat(style.fontSize);
124
+ const weight = Number.parseInt(style.fontWeight, 10);
125
+ const large = size >= LARGE_PX || (size >= LARGE_BOLD_PX && weight >= BOLD_WEIGHT);
126
+ return { ratio: (hi + 0.05) / (lo + 0.05), min: large ? MIN_CONTRAST_LARGE : MIN_CONTRAST };
127
+ };
128
+ const seenAt = (x, y, ...els) => {
129
+ if ('function' !== typeof document.elementFromPoint) {
130
+ return true;
131
+ }
132
+ const top = document.elementFromPoint(x, y);
133
+ return null !== top && els.some((el) => el === top || el.contains(top));
134
+ };
135
+ const INSET_PX = 2;
136
+ const visible = (el, r) => [
137
+ [r.left + r.width / 2, r.top + r.height / 2],
138
+ [r.left + INSET_PX, r.top + INSET_PX],
139
+ [r.right - INSET_PX, r.top + INSET_PX],
140
+ [r.left + INSET_PX, r.bottom - INSET_PX],
141
+ [r.right - INSET_PX, r.bottom - INSET_PX],
142
+ ].some(([x = 0, y = 0]) => seenAt(x, y, el));
143
+ const share = (a, b) => {
144
+ const w = Math.min(a.right, b.right) - Math.max(a.left, b.left);
145
+ const h = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top);
146
+ if (w <= 0 || h <= 0) {
147
+ return 0;
148
+ }
149
+ return (w * h) / Math.min(a.width * a.height, b.width * b.height);
150
+ };
151
+ export function auditLayout(root = document.body) {
152
+ const found = [];
153
+ const add = (kind, el, measured) => {
154
+ if (found.length < MAX_FINDINGS) {
155
+ found.push({ kind, ref: refs.refFor(el), what: whatOf(el), ...measured });
156
+ }
157
+ };
158
+ for (const el of root.querySelectorAll('*')) {
159
+ if (found.length >= MAX_FINDINGS) {
160
+ break;
161
+ }
162
+ if (isIgnored(el) || '' === ownText(el)) {
163
+ continue;
164
+ }
165
+ const rect = shown(el);
166
+ if (null === rect || !seenAt(rect.left + rect.width / 2, rect.top + rect.height / 2, el)) {
167
+ continue;
168
+ }
169
+ const cut = clipped(el);
170
+ if (null !== cut) {
171
+ add(LayoutDefect.CLIPPED, el, cut);
172
+ }
173
+ const contrast = contrastOf(el);
174
+ if (null !== contrast && contrast.ratio < contrast.min) {
175
+ add(LayoutDefect.CONTRAST, el, {
176
+ detail: `contrast ${contrast.ratio.toFixed(2)}:1, under ${String(contrast.min)}:1`,
177
+ value: Math.round(contrast.ratio * 100) / 100,
178
+ bound: contrast.min,
179
+ });
180
+ }
181
+ }
182
+ const controls = [...root.querySelectorAll(CONTROLS)]
183
+ .filter((el) => !isIgnored(el))
184
+ .slice(0, MAX_CONTROLS)
185
+ .flatMap((el) => {
186
+ const rect = shown(el);
187
+ return null === rect ? [] : [{ el, rect }];
188
+ });
189
+ for (const [i, a] of controls.entries()) {
190
+ for (const b of controls.slice(i + 1)) {
191
+ if (found.length >= MAX_FINDINGS) {
192
+ return found;
193
+ }
194
+ if (a.el.contains(b.el) || b.el.contains(a.el)) {
195
+ continue;
196
+ }
197
+ const covered = share(a.rect, b.rect);
198
+ if (covered >= OVERLAP_SHARE && visible(a.el, a.rect) && visible(b.el, b.rect)) {
199
+ add(LayoutDefect.OVERLAP, a.el, {
200
+ detail: `drawn over "${whatOf(b.el)}" (${refs.refFor(b.el)})`,
201
+ value: Math.round(covered * 100) / 100,
202
+ bound: OVERLAP_SHARE,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ return found;
208
+ }
@@ -4,3 +4,18 @@ export interface PaintContextEntry {
4
4
  readonly value: string;
5
5
  }
6
6
  export declare function paintContextOf(el: Element): PaintContextEntry[];
7
+ export type VisibleBounds = Pick<DOMRect, 'left' | 'right' | 'top' | 'bottom' | 'width' | 'height'>;
8
+ export interface VisibilityInfo {
9
+ hidden: boolean;
10
+ style: CSSStyleDeclaration | null;
11
+ parent: Element | null;
12
+ clipX: boolean;
13
+ clipY: boolean;
14
+ scrollX: boolean;
15
+ scrollY: boolean;
16
+ hasClipping: boolean;
17
+ clipBox?: VisibleBounds;
18
+ }
19
+ export declare function overflowAxes(style: CSSStyleDeclaration | null): [string, string];
20
+ export declare function clippingBox(el: Element, info: VisibilityInfo): VisibleBounds;
21
+ export declare function scrolledInto(b: VisibleBounds, port: VisibleBounds, alongX: boolean, alongY: boolean): VisibleBounds;
@@ -1,3 +1,4 @@
1
+ import { isHtmlElement } from './realm.js';
1
2
  const PAINT_PROPERTIES = [
2
3
  'filter',
3
4
  'opacity',
@@ -36,3 +37,80 @@ export function paintContextOf(el) {
36
37
  }
37
38
  return found;
38
39
  }
40
+ export function overflowAxes(style) {
41
+ const shorthand = style?.overflow ?? '';
42
+ const [x = 'visible', y = x] = ('' === shorthand ? 'visible' : shorthand).split(/\s+/);
43
+ return [
44
+ null === style || '' === style.overflowX ? x : style.overflowX,
45
+ null === style || '' === style.overflowY ? y : style.overflowY,
46
+ ];
47
+ }
48
+ export function clippingBox(el, info) {
49
+ if (info.clipBox !== undefined) {
50
+ return info.clipBox;
51
+ }
52
+ const rect = el.getBoundingClientRect();
53
+ const style = info.style;
54
+ const px = (value) => Number.parseFloat(value ?? '') || 0;
55
+ const border = (value, kind) => kind === undefined || '' === kind || 'none' === kind || 'hidden' === kind ? 0 : px(value);
56
+ const scaleX = isHtmlElement(el) && el.offsetWidth > 0 ? rect.width / el.offsetWidth : 1;
57
+ const scaleY = isHtmlElement(el) && el.offsetHeight > 0 ? rect.height / el.offsetHeight : 1;
58
+ const left = isHtmlElement(el) && el.offsetWidth > 0
59
+ ? rect.left + el.clientLeft * scaleX
60
+ : rect.left + border(style?.borderLeftWidth, style?.borderLeftStyle);
61
+ const top = isHtmlElement(el) && el.offsetHeight > 0
62
+ ? rect.top + el.clientTop * scaleY
63
+ : rect.top + border(style?.borderTopWidth, style?.borderTopStyle);
64
+ const right = isHtmlElement(el) && el.offsetWidth > 0
65
+ ? left + el.clientWidth * scaleX
66
+ : rect.right - border(style?.borderRightWidth, style?.borderRightStyle);
67
+ const bottom = isHtmlElement(el) && el.offsetHeight > 0
68
+ ? top + el.clientHeight * scaleY
69
+ : rect.bottom - border(style?.borderBottomWidth, style?.borderBottomStyle);
70
+ const margin = (style?.overflowClipMargin ?? '').split(/\s+/);
71
+ const outset = px(margin.at(-1));
72
+ const borderBox = margin.includes('border-box');
73
+ const contentBox = margin.includes('content-box');
74
+ const [x, y] = overflowAxes(style);
75
+ const clipLeft = 'clip' === x
76
+ ? (borderBox ? rect.left : left + (contentBox ? px(style?.paddingLeft) * scaleX : 0)) -
77
+ outset * scaleX
78
+ : left;
79
+ const clipRight = 'clip' === x
80
+ ? (borderBox ? rect.right : right - (contentBox ? px(style?.paddingRight) * scaleX : 0)) +
81
+ outset * scaleX
82
+ : right;
83
+ const clipTop = 'clip' === y
84
+ ? (borderBox ? rect.top : top + (contentBox ? px(style?.paddingTop) * scaleY : 0)) -
85
+ outset * scaleY
86
+ : top;
87
+ const clipBottom = 'clip' === y
88
+ ? (borderBox ? rect.bottom : bottom - (contentBox ? px(style?.paddingBottom) * scaleY : 0)) +
89
+ outset * scaleY
90
+ : bottom;
91
+ info.clipBox = {
92
+ left: clipLeft,
93
+ right: clipRight,
94
+ top: clipTop,
95
+ bottom: clipBottom,
96
+ width: clipRight - clipLeft,
97
+ height: clipBottom - clipTop,
98
+ };
99
+ return info.clipBox;
100
+ }
101
+ export function scrolledInto(b, port, alongX, alongY) {
102
+ const axis = (lo, hi, size, from, to) => {
103
+ if (hi > from && lo < to) {
104
+ return [lo, hi];
105
+ }
106
+ const span = Math.min(size, to - from);
107
+ return lo >= to ? [to - span, to] : [from, from + span];
108
+ };
109
+ const [left, right] = alongX
110
+ ? axis(b.left, b.right, b.width, port.left, port.right)
111
+ : [b.left, b.right];
112
+ const [top, bottom] = alongY
113
+ ? axis(b.top, b.bottom, b.height, port.top, port.bottom)
114
+ : [b.top, b.bottom];
115
+ return { left, right, top, bottom, width: right - left, height: bottom - top };
116
+ }
@@ -1,5 +1,6 @@
1
1
  import { ElementState, type ElementQuery, type MatchResult, type QueryResult } from '@reticlehq/core';
2
+ export declare function isHiddenNativeOption(el: HTMLElement): boolean;
2
3
  export declare const ATTR_VALUE_MAX = 512;
3
- export declare function matchQuery(query: ElementQuery, state?: ElementState, limit?: number): MatchResult;
4
+ export declare function matchQuery(query: ElementQuery, state?: ElementState, limit?: number, wholePhrase?: boolean, diagnose?: boolean): MatchResult;
4
5
  export declare function elementsMatching(query: ElementQuery): HTMLElement[];
5
6
  export declare function runQuery(query: ElementQuery, limit?: number): QueryResult;