@reticlehq/browser 3.4.0 → 3.5.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.
@@ -36,7 +36,6 @@ interface ActionResult {
36
36
  }
37
37
  export declare const ZOOM_NEEDS_REAL_BROWSER_MSG: string;
38
38
  export declare function executeAction(ref: string, action: string, args?: Record<string, unknown>): Promise<ActionResult>;
39
- export declare function dispatchWebMcp(tool: string, params: Record<string, unknown>, confirmDangerous?: boolean): Promise<unknown>;
40
39
  export interface ActionStep {
41
40
  ref: string;
42
41
  action: string;
@@ -410,16 +410,6 @@ export async function executeAction(ref, action, args = {}) {
410
410
  return result(ref, action, effect, settled, settleReason, anchor, warning);
411
411
  }
412
412
  const sleep = (ms) => new Promise((r) => nativeSetTimeout(r, ms));
413
- export async function dispatchWebMcp(tool, params, confirmDangerous = false) {
414
- if (requiresDangerousConfirmation(tool) && !confirmDangerous) {
415
- throw new Error(`potentially destructive WebMCP tool blocked; retry with ${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
416
- }
417
- const mc = navigator.modelContext;
418
- if (mc === undefined || typeof mc.callTool !== 'function') {
419
- throw new Error('WebMCP (navigator.modelContext) not available on this page');
420
- }
421
- return await mc.callTool(tool, params);
422
- }
423
413
  export async function executeSequence(steps) {
424
414
  const effects = [];
425
415
  const stepResults = [];
@@ -1,8 +1,8 @@
1
- import { ActionType, ComponentStateReason, DANGEROUS_ACTION_CONFIRM_ARG, ElementQuerySchema, ReticleCommand, SnapshotMode, TRANSPORT_LIMITS, selectPath, capDepth, } from '@reticlehq/core';
1
+ import { ComponentStateReason, ElementQuerySchema, ReticleCommand, SnapshotMode, TRANSPORT_LIMITS, selectPath, capDepth, } from '@reticlehq/core';
2
2
  import { buildSnapshot } from '../dom/snapshot.js';
3
3
  import { paintContextOf } from '../dom/paint-context.js';
4
4
  import { matchQuery, runQuery } from '../dom/query.js';
5
- import { executeAction, executeSequence, dispatchWebMcp, } from '../actions/actions.js';
5
+ import { executeAction, executeSequence } from '../actions/actions.js';
6
6
  import { describe } from '../dom/a11y.js';
7
7
  import { documentHasSourceStamps, sourceFor, formatSource } from '../dom/addressing/source.js';
8
8
  import { themeReport } from '../dom/theme.js';
@@ -198,10 +198,6 @@ export function createCommandRegistry() {
198
198
  reg.set(ReticleCommand.MATCH, (args) => matchQuery(ElementQuerySchema.parse(record(args['query'])), str(args['state'])));
199
199
  reg.set(ReticleCommand.ACT, (args) => {
200
200
  const action = str(args['action']) ?? '';
201
- if (action === ActionType.WEBMCP) {
202
- const inner = record(args['args']);
203
- return dispatchWebMcp(str(inner['tool']) ?? '', record(inner['params']), true === inner[DANGEROUS_ACTION_CONFIRM_ARG]);
204
- }
205
201
  return executeAction(str(args['ref']) ?? '', action, record(args['args']));
206
202
  });
207
203
  reg.set(ReticleCommand.ACT_SEQUENCE, (args) => executeSequence((Array.isArray(args['steps']) ? args['steps'] : [])));
package/dist/dom/a11y.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { ElementState, REDACTED_VALUE } from '@reticlehq/core';
2
2
  import { isButton, isHtmlElement, isImage, isInput, isMeter, isOutput, isProgress, isSelect, isTextArea, } from './realm.js';
3
3
  import { refs } from './addressing/refs.js';
4
+ import { capturedRootOf } from './shadow-registry.js';
4
5
  import { inspectChart } from './chart.js';
5
6
  import { isSensitiveKey } from '../security/serialization.js';
6
7
  import { formatSource, sourceFromDom } from './addressing/source.js';
7
8
  const HTML_DETAILS_TAG = 'details';
8
9
  const HTML_DETAILS_OPEN_ATTRIBUTE = 'open';
9
- const HTML_DIRECT_SUMMARY_SELECTOR = ':scope > summary';
10
+ const HTML_SUMMARY_TAG = 'summary';
10
11
  const NAME_FROM_CONTENT = new Set([
11
12
  'button',
12
13
  'link',
@@ -173,13 +174,19 @@ function textWithoutHidden(node) {
173
174
  const alt = el.getAttribute('alt');
174
175
  return null === alt ? '' : alt;
175
176
  }
176
- const parts = [];
177
+ let result = '';
178
+ let lastWasElement = false;
177
179
  for (const child of el.childNodes) {
178
180
  const piece = textWithoutHidden(child);
179
- if (piece.length > 0)
180
- parts.push(piece);
181
+ if (0 === piece.length)
182
+ continue;
183
+ const isEl = Node.ELEMENT_NODE === child.nodeType;
184
+ if (result.length > 0 && (isEl || lastWasElement))
185
+ result += ' ';
186
+ result += piece;
187
+ lastWasElement = isEl;
181
188
  }
182
- return parts.join(' ');
189
+ return result;
183
190
  }
184
191
  export function getAccessibleName(el) {
185
192
  const labelled = labelledByText(el);
@@ -299,8 +306,8 @@ function hiddenInsideClosedDetails(el) {
299
306
  const details = parent.closest(HTML_DETAILS_TAG);
300
307
  if (null === details || details.hasAttribute(HTML_DETAILS_OPEN_ATTRIBUTE))
301
308
  return false;
302
- const summary = details.querySelector(HTML_DIRECT_SUMMARY_SELECTOR);
303
- return null === summary || !summary.contains(el);
309
+ const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
310
+ return summary === undefined || !summary.contains(el);
304
311
  }
305
312
  function selfHidden(el) {
306
313
  if ('true' === el.getAttribute('aria-hidden'))
@@ -322,7 +329,23 @@ function selfHidden(el) {
322
329
  }
323
330
  return false;
324
331
  }
332
+ function slotInCapturedClosedRoot(el) {
333
+ const host = el.parentElement;
334
+ if (null === host || null !== host.shadowRoot)
335
+ return null;
336
+ const root = capturedRootOf(host);
337
+ if (null === root)
338
+ return null;
339
+ for (const slot of Array.from(root.querySelectorAll('slot'))) {
340
+ if (slot.assignedElements().includes(el))
341
+ return slot;
342
+ }
343
+ return null;
344
+ }
325
345
  function parentAcrossShadowBoundary(el) {
346
+ const slot = el.assignedSlot ?? slotInCapturedClosedRoot(el);
347
+ if (null !== slot)
348
+ return slot;
326
349
  if (null !== el.parentElement)
327
350
  return el.parentElement;
328
351
  const host = el.getRootNode().host;
@@ -14,9 +14,13 @@ export function hitTestOccluder(el, rect) {
14
14
  if (typeof doc.elementFromPoint !== 'function')
15
15
  return null;
16
16
  const withStack = doc;
17
+ const isOursButNotTheTarget = (candidate) => isReticleUi(candidate) &&
18
+ candidate !== el &&
19
+ !el.contains(candidate) &&
20
+ !candidate.contains(el);
17
21
  const topAt = (x, y) => {
18
22
  const found = withStack.elementsFromPoint?.(x, y) ?? [doc.elementFromPoint(x, y)];
19
- return found.find((c) => null !== c && !isReticleUi(c)) ?? null;
23
+ return found.find((c) => null !== c && !isOursButNotTheTarget(c)) ?? null;
20
24
  };
21
25
  const blockers = new Map();
22
26
  for (const [fx, fy] of SAMPLE_POINTS) {
package/dist/dom/query.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DATA_RETICLE_SOURCE_ATTR, ElementState, QueryBy, REDACTED_VALUE, TRANSPORT_LIMITS, } from '@reticlehq/core';
2
- import { isElement, isFrame, isHtmlElement, isInput, isSelect, isTextArea } from './realm.js';
2
+ import { isActionTarget, isElement, isFrame, isInput, isSelect, isTextArea } from './realm.js';
3
3
  import { capturedRootOf } from './shadow-registry.js';
4
4
  import { getAccessibleName, getRole, describe, getStates, isInViewport, isVisible, } from './a11y.js';
5
5
  import { isIgnored, isReticleOverlay } from './dom-ignore.js';
@@ -71,11 +71,11 @@ function resolveContainer(scope) {
71
71
  if (scope === undefined)
72
72
  return { container: document.body, scopeMissing: false };
73
73
  const byRef = refs.resolve(scope);
74
- if (isHtmlElement(byRef))
74
+ if (isActionTarget(byRef))
75
75
  return { container: byRef, scopeMissing: false };
76
76
  try {
77
77
  const found = document.querySelector(scope);
78
- if (isHtmlElement(found))
78
+ if (isActionTarget(found))
79
79
  return { container: found, scopeMissing: false };
80
80
  }
81
81
  catch {
@@ -269,7 +269,7 @@ export function matchQuery(query, state, limit = MAX_DESCRIBED) {
269
269
  const filtered = state === undefined ? elements : elements.filter((el) => inState(el, state, visMemo));
270
270
  const attrs = query.attrs;
271
271
  const described = filtered.slice(0, Math.max(0, Math.min(limit, MAX_DESCRIBED)));
272
- const stampViewport = described.length > 1;
272
+ const stampViewport = described.length > 1 || ElementState.IN_VIEWPORT === state;
273
273
  const descriptors = described.map((el) => {
274
274
  let base = describe(el, visMemo);
275
275
  if (stampViewport &&