@reticlehq/browser 2.2.1 → 2.4.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 (273) hide show
  1. package/dist/actions/actions-dom.d.ts +0 -1
  2. package/dist/actions/actions-dom.js +0 -1
  3. package/dist/actions/actions.d.ts +3 -1
  4. package/dist/actions/actions.js +55 -36
  5. package/dist/actions/scroll.d.ts +0 -1
  6. package/dist/actions/scroll.js +0 -1
  7. package/dist/actions/value-input.d.ts +33 -0
  8. package/dist/actions/value-input.js +63 -0
  9. package/dist/commands/commands.d.ts +0 -1
  10. package/dist/commands/commands.js +27 -7
  11. package/dist/connect-options.d.ts +97 -0
  12. package/dist/connect-options.js +1 -0
  13. package/dist/dom/a11y.d.ts +0 -2
  14. package/dist/dom/a11y.js +54 -24
  15. package/dist/dom/auto-anchor.d.ts +0 -1
  16. package/dist/dom/auto-anchor.js +0 -1
  17. package/dist/dom/blind-spots.d.ts +0 -1
  18. package/dist/dom/blind-spots.js +0 -1
  19. package/dist/dom/chart.d.ts +0 -1
  20. package/dist/dom/chart.js +0 -1
  21. package/dist/dom/desktop-capture.d.ts +36 -0
  22. package/dist/dom/desktop-capture.js +113 -0
  23. package/dist/dom/dom-ignore.d.ts +7 -1
  24. package/dist/dom/dom-ignore.js +1 -2
  25. package/dist/dom/occlusion.d.ts +0 -1
  26. package/dist/dom/occlusion.js +0 -1
  27. package/dist/dom/query.d.ts +0 -1
  28. package/dist/dom/query.js +61 -13
  29. package/dist/dom/realm.d.ts +30 -0
  30. package/dist/dom/realm.js +58 -0
  31. package/dist/dom/refs.d.ts +0 -1
  32. package/dist/dom/refs.js +0 -1
  33. package/dist/dom/shadow-registry.d.ts +40 -0
  34. package/dist/dom/shadow-registry.js +165 -0
  35. package/dist/dom/snapshot.d.ts +2 -2
  36. package/dist/dom/snapshot.js +21 -4
  37. package/dist/dom/source.d.ts +0 -1
  38. package/dist/dom/source.js +0 -1
  39. package/dist/dom/theme.d.ts +0 -1
  40. package/dist/dom/theme.js +0 -1
  41. package/dist/dom/virtualized.d.ts +19 -0
  42. package/dist/dom/virtualized.js +80 -0
  43. package/dist/index.d.ts +3 -4
  44. package/dist/index.js +2 -3
  45. package/dist/observers/animation.d.ts +0 -1
  46. package/dist/observers/animation.js +0 -1
  47. package/dist/observers/blind-spots.d.ts +16 -4
  48. package/dist/observers/blind-spots.js +65 -11
  49. package/dist/observers/console.d.ts +0 -1
  50. package/dist/observers/console.js +0 -1
  51. package/dist/observers/dom.d.ts +0 -2
  52. package/dist/observers/dom.js +67 -9
  53. package/dist/observers/download.d.ts +6 -0
  54. package/dist/observers/download.js +103 -0
  55. package/dist/observers/focus.d.ts +0 -1
  56. package/dist/observers/focus.js +0 -1
  57. package/dist/observers/frames.d.ts +12 -0
  58. package/dist/observers/frames.js +67 -0
  59. package/dist/observers/health.d.ts +0 -1
  60. package/dist/observers/health.js +30 -2
  61. package/dist/observers/install-all.d.ts +5 -0
  62. package/dist/observers/install-all.js +63 -0
  63. package/dist/observers/ipc.d.ts +22 -0
  64. package/dist/observers/ipc.js +121 -0
  65. package/dist/observers/network-body.d.ts +17 -1
  66. package/dist/observers/network-body.js +26 -1
  67. package/dist/observers/network-redact.d.ts +0 -1
  68. package/dist/observers/network-redact.js +6 -3
  69. package/dist/observers/network-stream.d.ts +23 -0
  70. package/dist/observers/network-stream.js +102 -0
  71. package/dist/observers/network.d.ts +11 -1
  72. package/dist/observers/network.js +7 -1
  73. package/dist/observers/perf.d.ts +0 -1
  74. package/dist/observers/perf.js +0 -1
  75. package/dist/observers/route.d.ts +0 -1
  76. package/dist/observers/route.js +0 -1
  77. package/dist/observers/scroll.d.ts +0 -1
  78. package/dist/observers/scroll.js +0 -1
  79. package/dist/observers/sdk-failure.d.ts +28 -0
  80. package/dist/observers/sdk-failure.js +60 -0
  81. package/dist/observers/state.d.ts +0 -1
  82. package/dist/observers/state.js +0 -1
  83. package/dist/observers/storage.d.ts +0 -1
  84. package/dist/observers/storage.js +0 -1
  85. package/dist/observers/types.d.ts +0 -1
  86. package/dist/observers/types.js +0 -1
  87. package/dist/patching/capture-method.d.ts +0 -1
  88. package/dist/patching/capture-method.js +0 -1
  89. package/dist/presenter/overlay.d.ts +0 -1
  90. package/dist/presenter/overlay.js +0 -1
  91. package/dist/presenter/presenter-config.d.ts +0 -1
  92. package/dist/presenter/presenter-config.js +0 -1
  93. package/dist/presenter/presenter-controls.d.ts +0 -1
  94. package/dist/presenter/presenter-controls.js +0 -1
  95. package/dist/presenter/presenter-effects.d.ts +0 -1
  96. package/dist/presenter/presenter-effects.js +0 -1
  97. package/dist/presenter/presenter-glow.d.ts +0 -1
  98. package/dist/presenter/presenter-glow.js +0 -1
  99. package/dist/presenter/presenter-log.d.ts +0 -1
  100. package/dist/presenter/presenter-log.js +0 -1
  101. package/dist/presenter/presenter-run-state.d.ts +0 -1
  102. package/dist/presenter/presenter-run-state.js +0 -1
  103. package/dist/presenter/presenter-styles.d.ts +0 -1
  104. package/dist/presenter/presenter-styles.js +0 -1
  105. package/dist/presenter/presenter-tally.d.ts +0 -1
  106. package/dist/presenter/presenter-tally.js +0 -1
  107. package/dist/presenter/presenter-test-helpers.d.ts +0 -1
  108. package/dist/presenter/presenter-test-helpers.js +0 -1
  109. package/dist/presenter/presenter-verbs.d.ts +0 -1
  110. package/dist/presenter/presenter-verbs.js +0 -1
  111. package/dist/presenter/presenter.d.ts +0 -1
  112. package/dist/presenter/presenter.js +0 -1
  113. package/dist/recorder/recorder-styles.d.ts +0 -1
  114. package/dist/recorder/recorder-styles.js +0 -1
  115. package/dist/recorder/recorder.d.ts +0 -1
  116. package/dist/recorder/recorder.js +0 -1
  117. package/dist/registry/adapters.d.ts +0 -1
  118. package/dist/registry/adapters.js +0 -1
  119. package/dist/registry/capabilities.d.ts +2 -1
  120. package/dist/registry/capabilities.js +16 -1
  121. package/dist/registry/commit-and-signal.d.ts +0 -1
  122. package/dist/registry/commit-and-signal.js +0 -1
  123. package/dist/registry/domains.d.ts +0 -1
  124. package/dist/registry/domains.js +0 -1
  125. package/dist/registry/emitter.d.ts +0 -1
  126. package/dist/registry/emitter.js +0 -1
  127. package/dist/registry/store-adapters.d.ts +141 -7
  128. package/dist/registry/store-adapters.js +178 -1
  129. package/dist/registry/stores.d.ts +0 -1
  130. package/dist/registry/stores.js +8 -1
  131. package/dist/reticle-presenter-helpers.d.ts +0 -1
  132. package/dist/reticle-presenter-helpers.js +0 -1
  133. package/dist/reticle.d.ts +5 -59
  134. package/dist/reticle.js +37 -33
  135. package/dist/review/annotator.d.ts +0 -1
  136. package/dist/review/annotator.js +0 -1
  137. package/dist/review/mark-anchor.d.ts +0 -1
  138. package/dist/review/mark-anchor.js +0 -1
  139. package/dist/security/serialization.d.ts +0 -1
  140. package/dist/security/serialization.js +0 -1
  141. package/dist/timers/clock.d.ts +0 -1
  142. package/dist/timers/clock.js +0 -1
  143. package/dist/timers/native-console.d.ts +0 -1
  144. package/dist/timers/native-console.js +0 -1
  145. package/dist/timers/native-timers.d.ts +0 -1
  146. package/dist/timers/native-timers.js +0 -1
  147. package/dist/transport/transport.d.ts +12 -6
  148. package/dist/transport/transport.js +107 -17
  149. package/package.json +13 -3
  150. package/dist/actions/actions-dom.d.ts.map +0 -1
  151. package/dist/actions/actions-dom.js.map +0 -1
  152. package/dist/actions/actions.d.ts.map +0 -1
  153. package/dist/actions/actions.js.map +0 -1
  154. package/dist/actions/scroll.d.ts.map +0 -1
  155. package/dist/actions/scroll.js.map +0 -1
  156. package/dist/commands/commands.d.ts.map +0 -1
  157. package/dist/commands/commands.js.map +0 -1
  158. package/dist/dom/a11y.d.ts.map +0 -1
  159. package/dist/dom/a11y.js.map +0 -1
  160. package/dist/dom/auto-anchor.d.ts.map +0 -1
  161. package/dist/dom/auto-anchor.js.map +0 -1
  162. package/dist/dom/blind-spots.d.ts.map +0 -1
  163. package/dist/dom/blind-spots.js.map +0 -1
  164. package/dist/dom/chart.d.ts.map +0 -1
  165. package/dist/dom/chart.js.map +0 -1
  166. package/dist/dom/dom-ignore.d.ts.map +0 -1
  167. package/dist/dom/dom-ignore.js.map +0 -1
  168. package/dist/dom/occlusion.d.ts.map +0 -1
  169. package/dist/dom/occlusion.js.map +0 -1
  170. package/dist/dom/query.d.ts.map +0 -1
  171. package/dist/dom/query.js.map +0 -1
  172. package/dist/dom/refs.d.ts.map +0 -1
  173. package/dist/dom/refs.js.map +0 -1
  174. package/dist/dom/snapshot.d.ts.map +0 -1
  175. package/dist/dom/snapshot.js.map +0 -1
  176. package/dist/dom/source.d.ts.map +0 -1
  177. package/dist/dom/source.js.map +0 -1
  178. package/dist/dom/theme.d.ts.map +0 -1
  179. package/dist/dom/theme.js.map +0 -1
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js.map +0 -1
  182. package/dist/observers/animation.d.ts.map +0 -1
  183. package/dist/observers/animation.js.map +0 -1
  184. package/dist/observers/blind-spots.d.ts.map +0 -1
  185. package/dist/observers/blind-spots.js.map +0 -1
  186. package/dist/observers/console.d.ts.map +0 -1
  187. package/dist/observers/console.js.map +0 -1
  188. package/dist/observers/dom.d.ts.map +0 -1
  189. package/dist/observers/dom.js.map +0 -1
  190. package/dist/observers/focus.d.ts.map +0 -1
  191. package/dist/observers/focus.js.map +0 -1
  192. package/dist/observers/health.d.ts.map +0 -1
  193. package/dist/observers/health.js.map +0 -1
  194. package/dist/observers/network-body.d.ts.map +0 -1
  195. package/dist/observers/network-body.js.map +0 -1
  196. package/dist/observers/network-redact.d.ts.map +0 -1
  197. package/dist/observers/network-redact.js.map +0 -1
  198. package/dist/observers/network.d.ts.map +0 -1
  199. package/dist/observers/network.js.map +0 -1
  200. package/dist/observers/perf.d.ts.map +0 -1
  201. package/dist/observers/perf.js.map +0 -1
  202. package/dist/observers/route.d.ts.map +0 -1
  203. package/dist/observers/route.js.map +0 -1
  204. package/dist/observers/scroll.d.ts.map +0 -1
  205. package/dist/observers/scroll.js.map +0 -1
  206. package/dist/observers/state.d.ts.map +0 -1
  207. package/dist/observers/state.js.map +0 -1
  208. package/dist/observers/storage.d.ts.map +0 -1
  209. package/dist/observers/storage.js.map +0 -1
  210. package/dist/observers/types.d.ts.map +0 -1
  211. package/dist/observers/types.js.map +0 -1
  212. package/dist/patching/capture-method.d.ts.map +0 -1
  213. package/dist/patching/capture-method.js.map +0 -1
  214. package/dist/presenter/overlay.d.ts.map +0 -1
  215. package/dist/presenter/overlay.js.map +0 -1
  216. package/dist/presenter/presenter-config.d.ts.map +0 -1
  217. package/dist/presenter/presenter-config.js.map +0 -1
  218. package/dist/presenter/presenter-controls.d.ts.map +0 -1
  219. package/dist/presenter/presenter-controls.js.map +0 -1
  220. package/dist/presenter/presenter-effects.d.ts.map +0 -1
  221. package/dist/presenter/presenter-effects.js.map +0 -1
  222. package/dist/presenter/presenter-glow.d.ts.map +0 -1
  223. package/dist/presenter/presenter-glow.js.map +0 -1
  224. package/dist/presenter/presenter-log.d.ts.map +0 -1
  225. package/dist/presenter/presenter-log.js.map +0 -1
  226. package/dist/presenter/presenter-run-state.d.ts.map +0 -1
  227. package/dist/presenter/presenter-run-state.js.map +0 -1
  228. package/dist/presenter/presenter-styles.d.ts.map +0 -1
  229. package/dist/presenter/presenter-styles.js.map +0 -1
  230. package/dist/presenter/presenter-tally.d.ts.map +0 -1
  231. package/dist/presenter/presenter-tally.js.map +0 -1
  232. package/dist/presenter/presenter-test-helpers.d.ts.map +0 -1
  233. package/dist/presenter/presenter-test-helpers.js.map +0 -1
  234. package/dist/presenter/presenter-verbs.d.ts.map +0 -1
  235. package/dist/presenter/presenter-verbs.js.map +0 -1
  236. package/dist/presenter/presenter.d.ts.map +0 -1
  237. package/dist/presenter/presenter.js.map +0 -1
  238. package/dist/recorder/recorder-styles.d.ts.map +0 -1
  239. package/dist/recorder/recorder-styles.js.map +0 -1
  240. package/dist/recorder/recorder.d.ts.map +0 -1
  241. package/dist/recorder/recorder.js.map +0 -1
  242. package/dist/registry/adapters.d.ts.map +0 -1
  243. package/dist/registry/adapters.js.map +0 -1
  244. package/dist/registry/capabilities.d.ts.map +0 -1
  245. package/dist/registry/capabilities.js.map +0 -1
  246. package/dist/registry/commit-and-signal.d.ts.map +0 -1
  247. package/dist/registry/commit-and-signal.js.map +0 -1
  248. package/dist/registry/domains.d.ts.map +0 -1
  249. package/dist/registry/domains.js.map +0 -1
  250. package/dist/registry/emitter.d.ts.map +0 -1
  251. package/dist/registry/emitter.js.map +0 -1
  252. package/dist/registry/store-adapters.d.ts.map +0 -1
  253. package/dist/registry/store-adapters.js.map +0 -1
  254. package/dist/registry/stores.d.ts.map +0 -1
  255. package/dist/registry/stores.js.map +0 -1
  256. package/dist/reticle-presenter-helpers.d.ts.map +0 -1
  257. package/dist/reticle-presenter-helpers.js.map +0 -1
  258. package/dist/reticle.d.ts.map +0 -1
  259. package/dist/reticle.js.map +0 -1
  260. package/dist/review/annotator.d.ts.map +0 -1
  261. package/dist/review/annotator.js.map +0 -1
  262. package/dist/review/mark-anchor.d.ts.map +0 -1
  263. package/dist/review/mark-anchor.js.map +0 -1
  264. package/dist/security/serialization.d.ts.map +0 -1
  265. package/dist/security/serialization.js.map +0 -1
  266. package/dist/timers/clock.d.ts.map +0 -1
  267. package/dist/timers/clock.js.map +0 -1
  268. package/dist/timers/native-console.d.ts.map +0 -1
  269. package/dist/timers/native-console.js.map +0 -1
  270. package/dist/timers/native-timers.d.ts.map +0 -1
  271. package/dist/timers/native-timers.js.map +0 -1
  272. package/dist/transport/transport.d.ts.map +0 -1
  273. package/dist/transport/transport.js.map +0 -1
package/dist/dom/a11y.js CHANGED
@@ -1,9 +1,22 @@
1
1
  import { ElementState, REDACTED_VALUE } from '@reticlehq/core';
2
+ import { isButton, isHtmlElement, isImage, isInput, isSelect, isTextArea } from './realm.js';
2
3
  import { refs } from './refs.js';
3
4
  import { inspectChart } from './chart.js';
4
5
  import { isSensitiveKey } from '../security/serialization.js';
5
6
  import { formatSource, sourceFromDom } from './source.js';
6
- /** Roles whose accessible name comes from their text content. */
7
+ /**
8
+ * Roles whose accessible name comes from their text content (ARIA's `nameFrom: author content`).
9
+ *
10
+ * `radio`, `checkbox`, `row` and `tooltip` were missing, and the gap is not cosmetic: a segmented
11
+ * filter written as `<button role="radio">held</button>` — an extremely ordinary design-system
12
+ * control — reported as a nameless `radio`, so `by: role` + name could not address it at all and an
13
+ * agent had to fall back to a testid the app has no reason to carry. Measured on a shipments console:
14
+ * six filters, six nameless radios, none reachable by name.
15
+ *
16
+ * `listitem`, `status` and `alert` are NOT in the spec's list. They are kept as a deliberate
17
+ * over-approximation — a name computed from content is more useful than no name — and removing them
18
+ * has no measured symptom to justify the risk.
19
+ */
7
20
  const NAME_FROM_CONTENT = new Set([
8
21
  'button',
9
22
  'link',
@@ -11,9 +24,13 @@ const NAME_FROM_CONTENT = new Set([
11
24
  'option',
12
25
  'listitem',
13
26
  'cell',
27
+ 'checkbox',
14
28
  'columnheader',
29
+ 'radio',
30
+ 'row',
15
31
  'rowheader',
16
32
  'tab',
33
+ 'tooltip',
17
34
  'menuitem',
18
35
  'menuitemcheckbox',
19
36
  'menuitemradio',
@@ -104,12 +121,36 @@ function labelledByText(el) {
104
121
  for (const id of ids.split(/\s+/)) {
105
122
  const ref = el.ownerDocument.getElementById(id);
106
123
  if (ref !== null)
107
- parts.push(collapse(ref.textContent ?? ''));
124
+ parts.push(collapse(textWithoutHidden(ref)));
108
125
  }
109
126
  const joined = parts.join(' ').trim();
110
127
  return joined.length > 0 ? joined : null;
111
128
  }
112
129
  /** Accessible name via a practical subset of the accname algorithm. */
130
+ /** Accessible name via a practical subset of the accname algorithm. */
131
+ /**
132
+ * Text content with `aria-hidden` subtrees removed.
133
+ *
134
+ * The accessible-name spec excludes them, and — decisively — so does dom-accessibility-api, which is
135
+ * what `by: role` + name MATCHES against via testing-library. Reading raw `textContent` here made the
136
+ * name we REPORT differ from the name that can be SELECTED: Material UI renders its required-field
137
+ * marker as `<span aria-hidden="true"> *</span>`, so a login field was reported as `"Username *"` and
138
+ * addressable only as `"Username"`. Reporting a name the agent cannot use defeats the purpose of
139
+ * reporting it at all, so both must come from the same rule.
140
+ */
141
+ function textWithoutHidden(node) {
142
+ if (node.nodeType === Node.TEXT_NODE)
143
+ return node.textContent ?? '';
144
+ if (node.nodeType !== Node.ELEMENT_NODE)
145
+ return '';
146
+ const el = node;
147
+ if (el.getAttribute('aria-hidden') === 'true')
148
+ return '';
149
+ let out = '';
150
+ for (const child of el.childNodes)
151
+ out += textWithoutHidden(child);
152
+ return out;
153
+ }
113
154
  export function getAccessibleName(el) {
114
155
  const labelled = labelledByText(el);
115
156
  if (labelled !== null)
@@ -117,31 +158,29 @@ export function getAccessibleName(el) {
117
158
  const ariaLabel = el.getAttribute('aria-label');
118
159
  if (ariaLabel !== null && ariaLabel.trim().length > 0)
119
160
  return ariaLabel.trim();
120
- if (el instanceof HTMLImageElement) {
161
+ if (isImage(el)) {
121
162
  const alt = el.getAttribute('alt');
122
163
  if (alt !== null)
123
164
  return alt.trim();
124
165
  }
125
- if (el instanceof HTMLInputElement ||
126
- el instanceof HTMLTextAreaElement ||
127
- el instanceof HTMLSelectElement) {
166
+ if (isInput(el) || isTextArea(el) || isSelect(el)) {
128
167
  const labels = el.labels;
129
168
  if (labels !== null && labels.length > 0) {
130
169
  const text = [...labels]
131
- .map((l) => collapse(l.textContent ?? ''))
170
+ .map((l) => collapse(textWithoutHidden(l)))
132
171
  .join(' ')
133
172
  .trim();
134
173
  if (text.length > 0)
135
174
  return text;
136
175
  }
137
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
176
+ if (isInput(el) || isTextArea(el)) {
138
177
  const placeholder = el.getAttribute('placeholder');
139
178
  if (placeholder !== null && placeholder.trim().length > 0)
140
179
  return placeholder.trim();
141
180
  }
142
181
  }
143
182
  if (NAME_FROM_CONTENT.has(getRole(el))) {
144
- const text = collapse(el.textContent ?? '');
183
+ const text = collapse(textWithoutHidden(el));
145
184
  if (text.length > 0)
146
185
  return text;
147
186
  }
@@ -163,14 +202,10 @@ function ariaBool(el, attr) {
163
202
  export function getStates(el, visible = isVisible(el)) {
164
203
  const states = [ElementState.PRESENT];
165
204
  states.push(visible ? ElementState.VISIBLE : ElementState.HIDDEN);
166
- const disabledProp = (el instanceof HTMLButtonElement ||
167
- el instanceof HTMLInputElement ||
168
- el instanceof HTMLSelectElement ||
169
- el instanceof HTMLTextAreaElement) &&
170
- el.disabled;
205
+ const disabledProp = (isButton(el) || isInput(el) || isSelect(el) || isTextArea(el)) && el.disabled;
171
206
  const disabled = disabledProp || ariaBool(el, 'aria-disabled') === true;
172
207
  states.push(disabled ? ElementState.DISABLED : ElementState.ENABLED);
173
- const checkedProp = el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio') && el.checked;
208
+ const checkedProp = isInput(el) && (el.type === 'checkbox' || el.type === 'radio') && el.checked;
174
209
  if (checkedProp || ariaBool(el, 'aria-checked') === true)
175
210
  states.push(ElementState.CHECKED);
176
211
  if (ariaBool(el, 'aria-expanded') === true)
@@ -186,9 +221,7 @@ export function getStates(el, visible = isVisible(el)) {
186
221
  * flow recorder's fill-value redaction, so recorded flows never persist typed passwords/OTPs/keys.
187
222
  */
188
223
  export function isSensitiveField(el) {
189
- if (!(el instanceof HTMLInputElement) &&
190
- !(el instanceof HTMLTextAreaElement) &&
191
- !(el instanceof HTMLSelectElement)) {
224
+ if (!isInput(el) && !isTextArea(el) && !isSelect(el)) {
192
225
  return false;
193
226
  }
194
227
  const autocomplete = el.getAttribute('autocomplete') ?? '';
@@ -199,14 +232,12 @@ export function isSensitiveField(el) {
199
232
  el.getAttribute('aria-label') ?? '',
200
233
  ];
201
234
  const sensitiveAutocomplete = /current-password|new-password|cc-number|cc-csc|one-time-code/i.test(autocomplete);
202
- return ((el instanceof HTMLInputElement && el.type.toLowerCase() === 'password') ||
235
+ return ((isInput(el) && el.type.toLowerCase() === 'password') ||
203
236
  sensitiveAutocomplete ||
204
237
  identifiers.some(isSensitiveKey));
205
238
  }
206
239
  export function getValue(el) {
207
- if (el instanceof HTMLInputElement ||
208
- el instanceof HTMLTextAreaElement ||
209
- el instanceof HTMLSelectElement) {
240
+ if (isInput(el) || isTextArea(el) || isSelect(el)) {
210
241
  if (isSensitiveField(el))
211
242
  return REDACTED_VALUE;
212
243
  return el.value;
@@ -218,7 +249,7 @@ export function getValue(el) {
218
249
  function selfHidden(el) {
219
250
  if (el.getAttribute('aria-hidden') === 'true')
220
251
  return true;
221
- if (el instanceof HTMLElement && el.hidden)
252
+ if (isHtmlElement(el) && el.hidden)
222
253
  return true;
223
254
  const view = el.ownerDocument.defaultView;
224
255
  if (view !== null) {
@@ -296,4 +327,3 @@ export function describe(el, memo) {
296
327
  }
297
328
  return base;
298
329
  }
299
- //# sourceMappingURL=a11y.js.map
@@ -63,4 +63,3 @@ interface SynthesizedAnchor {
63
63
  */
64
64
  export declare function synthesizeAnchor(input: AnchorInput): SynthesizedAnchor;
65
65
  export {};
66
- //# sourceMappingURL=auto-anchor.d.ts.map
@@ -72,4 +72,3 @@ export function synthesizeAnchor(input) {
72
72
  // Last resort: position only — addressable but never trusted.
73
73
  return { strategy: AnchorStrategy.POSITION, value: `el#${input.nth ?? 0}`, stable: false };
74
74
  }
75
- //# sourceMappingURL=auto-anchor.js.map
@@ -11,4 +11,3 @@ export declare function classifyCrossOriginFrames(frames: ReadonlyArray<{
11
11
  }>): number;
12
12
  /** Probe the live document: count iframes whose document is unreachable (cross-origin). */
13
13
  export declare function countCrossOriginFrames(): number;
14
- //# sourceMappingURL=blind-spots.d.ts.map
@@ -27,4 +27,3 @@ export function countCrossOriginFrames() {
27
27
  }
28
28
  return classifyCrossOriginFrames(frames);
29
29
  }
30
- //# sourceMappingURL=blind-spots.js.map
@@ -90,4 +90,3 @@ export declare function canvasChartData(canvas: Element, globals: {
90
90
  } | undefined;
91
91
  };
92
92
  }): CanvasChartData | null;
93
- //# sourceMappingURL=chart.d.ts.map
package/dist/dom/chart.js CHANGED
@@ -116,4 +116,3 @@ export function canvasChartData(canvas, globals) {
116
116
  }
117
117
  return null;
118
118
  }
119
- //# sourceMappingURL=chart.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Desktop screenshots, asked of the runtime that owns the window.
3
+ *
4
+ * A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
5
+ * to come from the shell — and the shell is the only party that can produce them CORRECTLY.
6
+ *
7
+ * Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
8
+ * window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
9
+ * later diff would trust. A screenshot tool that can silently return another window's pixels is
10
+ * worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
11
+ * eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
12
+ * correct while occluded, correct while backgrounded, and needing no screen-recording permission.
13
+ *
14
+ * Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
15
+ * Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
16
+ * `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
17
+ * the window occluded, hidden, or running headless with nothing on screen at all.
18
+ *
19
+ * The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
20
+ * (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
21
+ * rather than guessing.
22
+ */
23
+ export interface CaptureResult {
24
+ ok: boolean;
25
+ /**
26
+ * Filesystem path of the captured PNG, written by the desktop shell.
27
+ *
28
+ * A path, not the image bytes: the SDK's transport sanitizer caps every string at 64KB, so a
29
+ * base64 PNG came back SILENTLY TRUNCATED and was saved as a "successful" screenshot that no
30
+ * decoder could read. The daemon and the app always share a machine here (the bridge is loopback),
31
+ * so handing over a path keeps the image off the event wire entirely.
32
+ */
33
+ path?: string;
34
+ reason?: string;
35
+ }
36
+ export declare function captureDesktopWindow(fullPage?: boolean): Promise<CaptureResult>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Desktop screenshots, asked of the runtime that owns the window.
3
+ *
4
+ * A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
5
+ * to come from the shell — and the shell is the only party that can produce them CORRECTLY.
6
+ *
7
+ * Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
8
+ * window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
9
+ * later diff would trust. A screenshot tool that can silently return another window's pixels is
10
+ * worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
11
+ * eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
12
+ * correct while occluded, correct while backgrounded, and needing no screen-recording permission.
13
+ *
14
+ * Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
15
+ * Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
16
+ * `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
17
+ * the window occluded, hidden, or running headless with nothing on screen at all.
18
+ *
19
+ * The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
20
+ * (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
21
+ * rather than guessing.
22
+ */
23
+ import { RETICLE_IPC_GLOBAL, RETICLE_TAURI_CAPTURE_COMMAND, VisualReason } from '@reticlehq/core';
24
+ import { nativeFrame } from '../timers/native-timers.js';
25
+ import { RETICLE_OVERLAY } from './dom-ignore.js';
26
+ /**
27
+ * What a shell returns when it can photograph the viewport but not the whole scroll height.
28
+ *
29
+ * Only WebKitGTK can render a full document offscreen; `capturePage()` and `takeSnapshot` both give
30
+ * what is composited. Downgrading silently would be the worst option available: the caller asked for
31
+ * the content below the fold, would get an image without it, and would bank it as a baseline that a
32
+ * later diff reports as matching. So the shells say so, and the tool reports the reason.
33
+ */
34
+ const FULL_PAGE_UNSUPPORTED_MARKER = VisualReason.FULL_PAGE_UNSUPPORTED;
35
+ const TAURI_INTERNALS_GLOBAL = '__TAURI_INTERNALS__';
36
+ /**
37
+ * Tauri's capture path, used when no preload-installed channel exists.
38
+ *
39
+ * Electron can install `RETICLE_IPC_GLOBAL` from its preload, which runs before app code. Tauri has
40
+ * no preload stage at all, so requiring the same global would mean every Tauri app hand-writing a
41
+ * frontend shim — a setup step that silently yields "no screenshots" when forgotten. Invoking the
42
+ * command directly removes the JavaScript side entirely: the app registers one Rust command and the
43
+ * SDK finds it. Absent that command the invoke rejects, and this reports no-provider like any other.
44
+ */
45
+ function tauriCapture() {
46
+ const internals = window[TAURI_INTERNALS_GLOBAL];
47
+ if (typeof internals?.invoke !== 'function')
48
+ return undefined;
49
+ const invoke = internals.invoke.bind(internals);
50
+ return async (fullPage) => {
51
+ const path = await invoke(RETICLE_TAURI_CAPTURE_COMMAND, { fullPage: fullPage === true });
52
+ return typeof path === 'string' ? path : null;
53
+ };
54
+ }
55
+ /**
56
+ * Take Reticle's own UI out of the shot, then put it back.
57
+ *
58
+ * The shell photographs the composited window, so anything Reticle mounted is IN the picture — and
59
+ * the presenter panel is the worst possible thing to bank into a visual baseline, because it renders
60
+ * live session state: an event tally, a log tail, a run badge. A baseline captured with it is a
61
+ * baseline of Reticle's own UI as much as the app's, and the next `reticle_visual_diff` reports a
62
+ * change that belongs entirely to the observer. That is measuring the instrument.
63
+ *
64
+ * Two frames, not one: the first lets the style change apply, the second lets the compositor present
65
+ * it — capture before that and the shell photographs the pre-hide window anyway. Both are BOUNDED
66
+ * frames, which is load-bearing rather than defensive: a headless Tauri window is hidden, and a
67
+ * hidden webview never fires `requestAnimationFrame` at all, so a raw double-rAF here deadlocked
68
+ * every capture until the command timed out at 8s. Restoration runs in a `finally`, so a capture
69
+ * that throws still leaves the panel on screen for the human watching.
70
+ */
71
+ async function withReticleUiHidden(capture) {
72
+ // The SAME selector the snapshot layer already uses to keep Reticle's own DOM out of what the
73
+ // agent reads — the presenter panel, the cursor, the glow, and the annotator's "flag a bug"
74
+ // button. One definition: a root that leaked into snapshots once would otherwise leak into
75
+ // baselines the second time someone added one.
76
+ const roots = [...document.querySelectorAll(RETICLE_OVERLAY)];
77
+ const previous = roots.map((el) => el.style.visibility);
78
+ // `visibility`, not `display`: it keeps the element's box, so hiding cannot reflow the app
79
+ // underneath and change the very layout the screenshot is meant to record.
80
+ for (const el of roots)
81
+ el.style.visibility = 'hidden';
82
+ try {
83
+ if (roots.length > 0) {
84
+ await nativeFrame();
85
+ await nativeFrame();
86
+ }
87
+ return await capture();
88
+ }
89
+ finally {
90
+ roots.forEach((el, i) => {
91
+ el.style.visibility = previous[i] ?? '';
92
+ });
93
+ }
94
+ }
95
+ export async function captureDesktopWindow(fullPage = false) {
96
+ const channel = window[RETICLE_IPC_GLOBAL];
97
+ const capture = typeof channel?.capture === 'function' ? channel.capture : tauriCapture();
98
+ if (capture === undefined) {
99
+ return { ok: false, reason: 'no desktop capture helper installed' };
100
+ }
101
+ try {
102
+ const path = await withReticleUiHidden(() => capture(fullPage));
103
+ return typeof path === 'string' && path.length > 0
104
+ ? { ok: true, path }
105
+ : { ok: false, reason: 'capture returned no image' };
106
+ }
107
+ catch (error) {
108
+ const reason = error instanceof Error ? error.message : String(error);
109
+ return reason.includes(FULL_PAGE_UNSUPPORTED_MARKER)
110
+ ? { ok: false, reason: FULL_PAGE_UNSUPPORTED_MARKER }
111
+ : { ok: false, reason };
112
+ }
113
+ }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Selectors for Reticle's own presenter overlay (cursor, HUD, glow) + the annotator's "flag a bug"
3
+ * UI (`data-reticle-mark`) — never observed/snapshotted. The annotator mounts by DEFAULT with the
4
+ * presenter, so omitting its selector here leaked a phantom `<button>Flag a bug</button>` into every
5
+ * snapshot and streamed its highlight-box mutations/transitions as if the APP had rendered them.
6
+ */
7
+ export declare const RETICLE_OVERLAY = "[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark]";
1
8
  /** Let the host app add selectors to exclude from snapshots (e.g. its own dev widgets). */
2
9
  export declare function setIgnoreSelectors(selectors: string[]): void;
3
10
  /** True if the element is part of Reticle's own presenter overlay. */
@@ -6,4 +13,3 @@ export declare function isReticleOverlay(el: Element): boolean;
6
13
  export declare function isReticleUi(node: Element | null): boolean;
7
14
  /** True if the element should be excluded from snapshots/queries (Reticle overlay or dev overlay). */
8
15
  export declare function isIgnored(el: Element): boolean;
9
- //# sourceMappingURL=dom-ignore.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * presenter, so omitting its selector here leaked a phantom `<button>Flag a bug</button>` into every
5
5
  * snapshot and streamed its highlight-box mutations/transitions as if the APP had rendered them.
6
6
  */
7
- const RETICLE_OVERLAY = '[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark]';
7
+ export const RETICLE_OVERLAY = '[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark]';
8
8
  /** Known third-party dev overlays to keep out of snapshots (Agentation, Next dev UI). */
9
9
  const DEV_OVERLAYS = '[data-agentation],#__next-build-watcher,nextjs-portal,[data-nextjs-dialog],[data-nextjs-toast]';
10
10
  let extraIgnore = '';
@@ -33,4 +33,3 @@ export function isIgnored(el) {
33
33
  : `${RETICLE_OVERLAY},${DEV_OVERLAYS}`;
34
34
  return el.closest(sel) !== null;
35
35
  }
36
- //# sourceMappingURL=dom-ignore.js.map
@@ -6,4 +6,3 @@
6
6
  * we never false-positive an occlusion. Reticle's own HUD is never treated as an occluder.
7
7
  */
8
8
  export declare function hitTestOccluder(el: Element, rect: DOMRect): Element | null;
9
- //# sourceMappingURL=occlusion.d.ts.map
@@ -18,4 +18,3 @@ export function hitTestOccluder(el, rect) {
18
18
  const lands = top === el || el.contains(top) || top.contains(el);
19
19
  return lands ? null : top;
20
20
  }
21
- //# sourceMappingURL=occlusion.js.map
@@ -16,4 +16,3 @@ export declare function matchQuery(query: ElementQuery, state?: ElementState, li
16
16
  * do — silently put the server back to counting survivors and calling that the answer.
17
17
  */
18
18
  export declare function runQuery(query: ElementQuery, limit?: number): QueryResult;
19
- //# sourceMappingURL=query.d.ts.map
package/dist/dom/query.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { queryAllByRole, queryAllByText, queryAllByLabelText, queryAllByPlaceholderText, queryAllByTestId, queryAllByAltText, } from '@testing-library/dom';
2
2
  import { DATA_RETICLE_SOURCE_ATTR, ElementState, QueryBy, REDACTED_VALUE, TRANSPORT_LIMITS, } from '@reticlehq/core';
3
+ import { isFrame, isHtmlElement } from './realm.js';
4
+ import { capturedRootOf } from './shadow-registry.js';
3
5
  import { describe, getStates, isVisible } from './a11y.js';
6
+ import { isIgnored } from './dom-ignore.js';
4
7
  import { isSensitiveKey } from '../security/serialization.js';
5
8
  import { getCapabilities } from '../registry/capabilities.js';
6
9
  import { identifyComponent } from '../registry/adapters.js';
@@ -22,11 +25,11 @@ function resolveContainer(scope) {
22
25
  if (scope === undefined)
23
26
  return { container: document.body, scopeMissing: false };
24
27
  const byRef = refs.resolve(scope);
25
- if (byRef instanceof HTMLElement)
28
+ if (isHtmlElement(byRef))
26
29
  return { container: byRef, scopeMissing: false };
27
30
  try {
28
31
  const found = document.querySelector(scope);
29
- if (found instanceof HTMLElement)
32
+ if (isHtmlElement(found))
30
33
  return { container: found, scopeMissing: false };
31
34
  }
32
35
  catch {
@@ -138,18 +141,56 @@ function findIn(container, query) {
138
141
  * A closed root is deliberately unreachable — `element.shadowRoot` is null by design, and the SDK
139
142
  * reports that as a blind spot rather than pretending to see through it.
140
143
  */
141
- function openShadowRootsUnder(root) {
144
+ /**
145
+ * A same-origin `<iframe>`'s body, or null when the document is unreachable.
146
+ *
147
+ * Cross-origin access THROWS in some engines and yields null in others, so both are handled — and
148
+ * both mean the same thing here: not ours to read. That case is not a silent skip, it is reported
149
+ * separately as a declared blind spot.
150
+ */
151
+ function readableFrameBody(frame) {
152
+ try {
153
+ return frame.contentDocument?.body ?? null;
154
+ }
155
+ catch {
156
+ return null;
157
+ }
158
+ }
159
+ /** How deep to follow frames-inside-frames before giving up. Real consoles nest one, maybe two. */
160
+ const FRAME_DEPTH_MAX = 3;
161
+ /**
162
+ * Every root beneath the scope that the page can read but `querySelectorAll` will not reach:
163
+ * open shadow roots, and the documents of SAME-ORIGIN iframes.
164
+ *
165
+ * The iframe half was measured missing: a same-origin frame whose text the page could read straight
166
+ * off `contentDocument` returned zero matches from `query`, and coverage said nothing about it — the
167
+ * worst combination, an absent-element answer for content that is right there. Cross-origin frames
168
+ * are a genuinely different case and stay unread; they are declared instead.
169
+ */
170
+ function embeddedRootsUnder(root) {
142
171
  const found = [];
143
- const walk = (node) => {
172
+ const walk = (node, depth) => {
144
173
  for (const el of node.querySelectorAll('*')) {
145
- const shadow = el.shadowRoot;
174
+ const shadow = el.shadowRoot ?? capturedRootOf(el);
146
175
  if (shadow !== null) {
176
+ // A ShadowRoot is a DocumentFragment; the Testing-Library queries accept any HTMLElement-like
177
+ // container, and every call site only reads from it.
147
178
  found.push(shadow);
148
- walk(shadow); // nested web components
179
+ walk(shadow, depth); // nested web components
180
+ continue;
149
181
  }
182
+ if (depth >= FRAME_DEPTH_MAX)
183
+ continue;
184
+ if (!isFrame(el))
185
+ continue;
186
+ const body = readableFrameBody(el);
187
+ if (body === null)
188
+ continue; // cross-origin — declared, not searched
189
+ found.push(body);
190
+ walk(body, depth + 1);
150
191
  }
151
192
  };
152
- walk(root);
193
+ walk(root, 0);
153
194
  return found;
154
195
  }
155
196
  /**
@@ -185,16 +226,22 @@ function findCandidates(query) {
185
226
  for (const el of els) {
186
227
  if (seen.has(el))
187
228
  continue; // reachable from both host and root — count once
229
+ // Reticle's OWN UI is not part of the app under test. `snapshot` has always excluded it; query
230
+ // did not, so the presenter panel and the annotator answered `by: role` like app controls —
231
+ // measured on a real merchant dashboard, 7 of the 40 buttons an agent could see were ours
232
+ // ("Pause", "End", "Minimise the panel", "Export", "Flag a bug for the agent"). Two of those
233
+ // share a NAME with a real control on that page, so an agent resolving "Export" could drive the
234
+ // observer instead of the app and then reason about the result. Filtered here, at the one
235
+ // collection point, so every entry path (by role/text/testid, shadow roots, scoped) is covered.
236
+ if (isIgnored(el))
237
+ continue;
188
238
  seen.add(el);
189
239
  out.push(el);
190
240
  }
191
241
  };
192
242
  collect(findIn(container, query));
193
- for (const shadow of openShadowRootsUnder(container)) {
194
- // A ShadowRoot is a DocumentFragment; the Testing-Library queries accept any HTMLElement-like
195
- // container, and every call site below only reads from it.
196
- collect(findIn(shadow, query));
197
- }
243
+ for (const embedded of embeddedRootsUnder(container))
244
+ collect(findIn(embedded, query));
198
245
  return { candidates: out, scopeMissing };
199
246
  }
200
247
  /** Longest attribute value returned; one enormous href must not blow the response budget. */
@@ -351,6 +398,8 @@ function buildEmptyHint(query) {
351
398
  const all = container.querySelectorAll(`[${TESTID_ATTR}]`);
352
399
  const present = [];
353
400
  for (const el of Array.from(all)) {
401
+ if (isIgnored(el))
402
+ continue; // the "what IS here" hint must not advertise Reticle's own UI either
354
403
  const id = el.getAttribute(TESTID_ATTR);
355
404
  if (id !== null && id.length > 0 && !present.includes(id)) {
356
405
  present.push(id);
@@ -388,4 +437,3 @@ export function runQuery(query, limit) {
388
437
  }
389
438
  return { elements: result.elements, count: result.count, ...scopeFields };
390
439
  }
391
- //# sourceMappingURL=query.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Cross-realm type tests.
3
+ *
4
+ * `instanceof` compares against ONE realm's constructor. An element inside a same-origin `<iframe>`
5
+ * lives in that document's realm, so `frameButton instanceof HTMLElement` — evaluated with the top
6
+ * window's `HTMLElement` — is FALSE for a perfectly ordinary button.
7
+ *
8
+ * That is not theoretical. Once `query` learned to descend into same-origin frames, it returned the
9
+ * frame's button correctly and `act` then rejected the ref with "is not an HTMLElement": found, and
10
+ * unusable. A find you cannot act on is worse than a miss, because it reads as a product bug in the
11
+ * app rather than in the tool.
12
+ *
13
+ * These resolve the constructor from the NODE's own document instead, so an element is measured
14
+ * against its own realm. Same-realm nodes behave exactly as `instanceof` did.
15
+ */
16
+ export declare const isElement: (n: unknown) => n is Element;
17
+ export declare const isImage: (n: unknown) => n is HTMLImageElement;
18
+ export declare const isHtmlElement: (n: unknown) => n is HTMLElement;
19
+ export declare const isInput: (n: unknown) => n is HTMLInputElement;
20
+ export declare const isTextArea: (n: unknown) => n is HTMLTextAreaElement;
21
+ export declare const isSelect: (n: unknown) => n is HTMLSelectElement;
22
+ export declare const isButton: (n: unknown) => n is HTMLButtonElement;
23
+ export declare const isForm: (n: unknown) => n is HTMLFormElement;
24
+ export declare const isFrame: (n: unknown) => n is HTMLIFrameElement;
25
+ /**
26
+ * The prototype whose native value setter drives `el` — React and friends install their own accessor
27
+ * on the instance, so a value write has to go through the prototype's. It must come from the
28
+ * ELEMENT's realm for the same reason as above.
29
+ */
30
+ export declare function valuePrototypeOf(el: Element): object | undefined;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Cross-realm type tests.
3
+ *
4
+ * `instanceof` compares against ONE realm's constructor. An element inside a same-origin `<iframe>`
5
+ * lives in that document's realm, so `frameButton instanceof HTMLElement` — evaluated with the top
6
+ * window's `HTMLElement` — is FALSE for a perfectly ordinary button.
7
+ *
8
+ * That is not theoretical. Once `query` learned to descend into same-origin frames, it returned the
9
+ * frame's button correctly and `act` then rejected the ref with "is not an HTMLElement": found, and
10
+ * unusable. A find you cannot act on is worse than a miss, because it reads as a product bug in the
11
+ * app rather than in the tool.
12
+ *
13
+ * These resolve the constructor from the NODE's own document instead, so an element is measured
14
+ * against its own realm. Same-realm nodes behave exactly as `instanceof` did.
15
+ */
16
+ /** The constructor named `name` in the realm that owns `node`, or undefined if there isn't one. */
17
+ function ctorIn(node, name) {
18
+ if (node === null || typeof node !== 'object')
19
+ return undefined;
20
+ // A Document owns itself; every other node reports an ownerDocument.
21
+ const doc = node.ownerDocument ?? node;
22
+ const view = doc.defaultView;
23
+ if (view === null || view === undefined)
24
+ return undefined;
25
+ return view[name];
26
+ }
27
+ /**
28
+ * `node instanceof <name>`, evaluated in the node's own realm.
29
+ *
30
+ * Falls back to the ambient constructor when the node has no view — a detached document, or a jsdom
31
+ * fragment — so behaviour outside a browser is unchanged.
32
+ */
33
+ function isIn(node, name, ambient) {
34
+ const ctor = ctorIn(node, name) ?? ambient;
35
+ return typeof ctor === 'function' && node instanceof ctor;
36
+ }
37
+ export const isElement = (n) => isIn(n, 'Element', typeof Element === 'undefined' ? undefined : Element);
38
+ export const isImage = (n) => isIn(n, 'HTMLImageElement', typeof HTMLImageElement === 'undefined' ? undefined : HTMLImageElement);
39
+ export const isHtmlElement = (n) => isIn(n, 'HTMLElement', typeof HTMLElement === 'undefined' ? undefined : HTMLElement);
40
+ export const isInput = (n) => isIn(n, 'HTMLInputElement', typeof HTMLInputElement === 'undefined' ? undefined : HTMLInputElement);
41
+ export const isTextArea = (n) => isIn(n, 'HTMLTextAreaElement', typeof HTMLTextAreaElement === 'undefined' ? undefined : HTMLTextAreaElement);
42
+ export const isSelect = (n) => isIn(n, 'HTMLSelectElement', typeof HTMLSelectElement === 'undefined' ? undefined : HTMLSelectElement);
43
+ export const isButton = (n) => isIn(n, 'HTMLButtonElement', typeof HTMLButtonElement === 'undefined' ? undefined : HTMLButtonElement);
44
+ export const isForm = (n) => isIn(n, 'HTMLFormElement', typeof HTMLFormElement === 'undefined' ? undefined : HTMLFormElement);
45
+ export const isFrame = (n) => isIn(n, 'HTMLIFrameElement', typeof HTMLIFrameElement === 'undefined' ? undefined : HTMLIFrameElement);
46
+ /**
47
+ * The prototype whose native value setter drives `el` — React and friends install their own accessor
48
+ * on the instance, so a value write has to go through the prototype's. It must come from the
49
+ * ELEMENT's realm for the same reason as above.
50
+ */
51
+ export function valuePrototypeOf(el) {
52
+ const name = isTextArea(el) ? 'HTMLTextAreaElement' : 'HTMLInputElement';
53
+ const ctor = ctorIn(el, name);
54
+ if (typeof ctor !== 'function')
55
+ return undefined;
56
+ const proto = ctor.prototype;
57
+ return typeof proto === 'object' && proto !== null ? proto : undefined;
58
+ }
@@ -35,4 +35,3 @@ export declare class RefRegistry {
35
35
  }
36
36
  /** Process-wide registry shared by snapshot, query, and the action executor. */
37
37
  export declare const refs: RefRegistry;
38
- //# sourceMappingURL=refs.d.ts.map
package/dist/dom/refs.js CHANGED
@@ -108,4 +108,3 @@ export class RefRegistry {
108
108
  }
109
109
  /** Process-wide registry shared by snapshot, query, and the action executor. */
110
110
  export const refs = new RefRegistry();
111
- //# sourceMappingURL=refs.js.map