@reticlehq/browser 3.8.2 → 3.10.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 (60) hide show
  1. package/dist/actions/action-report.d.ts +14 -0
  2. package/dist/actions/action-report.js +95 -22
  3. package/dist/actions/actions-dom.d.ts +17 -6
  4. package/dist/actions/actions-dom.js +98 -47
  5. package/dist/actions/actions-press.d.ts +10 -0
  6. package/dist/actions/actions-press.js +146 -2
  7. package/dist/actions/actions.js +64 -24
  8. package/dist/actions/insert-text.d.ts +3 -0
  9. package/dist/actions/insert-text.js +90 -0
  10. package/dist/actions/scroll.d.ts +1 -1
  11. package/dist/actions/scroll.js +25 -1
  12. package/dist/actions/value-input.d.ts +2 -0
  13. package/dist/actions/value-input.js +30 -9
  14. package/dist/commands/commands.js +85 -9
  15. package/dist/connect-options.d.ts +11 -1
  16. package/dist/connect-options.js +27 -1
  17. package/dist/dom/a11y.d.ts +1 -1
  18. package/dist/dom/a11y.js +11 -10
  19. package/dist/dom/chart-geometry.d.ts +7 -0
  20. package/dist/dom/chart-geometry.js +39 -0
  21. package/dist/dom/occlusion.js +21 -0
  22. package/dist/dom/query-finders.d.ts +1 -1
  23. package/dist/dom/query-finders.js +10 -7
  24. package/dist/dom/query.d.ts +6 -2
  25. package/dist/dom/query.js +165 -54
  26. package/dist/dom/scope-roots.d.ts +8 -0
  27. package/dist/dom/scope-roots.js +44 -0
  28. package/dist/dom/snapshot.d.ts +3 -0
  29. package/dist/dom/snapshot.js +9 -2
  30. package/dist/dom/text-match.js +6 -4
  31. package/dist/dom/theme.js +6 -7
  32. package/dist/dom/visibility.d.ts +3 -0
  33. package/dist/dom/visibility.js +125 -10
  34. package/dist/observers/canvas.d.ts +1 -0
  35. package/dist/observers/canvas.js +10 -1
  36. package/dist/observers/console.js +46 -35
  37. package/dist/observers/dom.js +28 -4
  38. package/dist/observers/net-detail/resource-timing.d.ts +12 -0
  39. package/dist/observers/net-detail/resource-timing.js +69 -0
  40. package/dist/observers/network.js +13 -44
  41. package/dist/observers/perf.js +15 -1
  42. package/dist/presenter/chrome/presenter-verbs.js +1 -0
  43. package/dist/presenter/presenter-config.d.ts +1 -0
  44. package/dist/presenter/presenter-config.js +1 -0
  45. package/dist/presenter/presenter-shell.d.ts +1 -0
  46. package/dist/presenter/presenter-shell.js +6 -3
  47. package/dist/presenter/tour/tour.js +4 -0
  48. package/dist/recorder/recorder.js +82 -39
  49. package/dist/reticle-inject.js +618 -618
  50. package/dist/reticle.d.ts +3 -19
  51. package/dist/reticle.js +76 -110
  52. package/dist/review/annotator.js +4 -0
  53. package/dist/security/serialization.js +35 -0
  54. package/dist/session-continuity.d.ts +1 -0
  55. package/dist/session-continuity.js +18 -0
  56. package/dist/storage-keys.js +2 -1
  57. package/dist/timers/native/native-timers.js +21 -4
  58. package/dist/transport/bridge-url.d.ts +8 -0
  59. package/dist/transport/bridge-url.js +52 -0
  60. package/package.json +10 -10
@@ -31,7 +31,12 @@ export interface ActionResult {
31
31
  effect: ActionEffect;
32
32
  testid?: string;
33
33
  testidOf?: number;
34
+ testidNth?: number;
34
35
  component?: string;
36
+ componentNth?: number;
37
+ componentOf?: number;
38
+ ownText?: string;
39
+ within?: string[];
35
40
  role?: string;
36
41
  name?: string;
37
42
  nth?: number;
@@ -48,17 +53,26 @@ export interface ActionResult {
48
53
  export interface CapturedAnchor {
49
54
  testid?: string;
50
55
  testidOf?: number;
56
+ testidNth?: number;
51
57
  component?: string;
52
58
  source?: {
53
59
  file: string;
54
60
  line: number;
55
61
  column?: number;
56
62
  };
63
+ componentNth?: number;
64
+ componentOf?: number;
65
+ ownText?: string;
57
66
  role?: string;
58
67
  name?: string;
59
68
  nth?: number;
60
69
  of?: number;
70
+ within?: string[];
61
71
  secret?: true;
62
72
  }
73
+ export declare function positionAmongNamesakes(el: Element, role: string, name: string): {
74
+ nth: number;
75
+ of: number;
76
+ } | null;
63
77
  export declare function anchorOf(el: Element): CapturedAnchor;
64
78
  export declare const result: (ref: string, action: string, effect: ActionEffect, settled: boolean, settleReason: SettleReason | null, anchor: CapturedAnchor, warning?: string) => ActionResult;
@@ -1,36 +1,80 @@
1
1
  import { readTestId } from '../dom/addressing/testid-attr.js';
2
2
  import { QueryBy } from '@reticlehq/core';
3
3
  import { elementsMatching } from '../dom/query.js';
4
+ import { ownWords } from '../dom/query-finders.js';
4
5
  import { getAccessibleName, getRole, isSensitiveField } from '../dom/a11y.js';
5
6
  import { identifyComponent } from '../registry/stores/adapters.js';
6
7
  import { isReticleUi } from '../dom/dom-ignore.js';
8
+ import { isInViewport } from '../dom/visibility.js';
7
9
  const CANVAS_SELECTOR = 'canvas';
8
10
  export function visibleCanvases(doc) {
9
- const width = doc.defaultView?.innerWidth ?? 0;
10
- const height = doc.defaultView?.innerHeight ?? 0;
11
- let count = 0;
12
- for (const canvas of doc.querySelectorAll(CANVAS_SELECTOR)) {
13
- if (isReticleUi(canvas)) {
14
- continue;
15
- }
16
- const box = canvas.getBoundingClientRect();
17
- const sized = box.width > 0 && box.height > 0;
18
- const onScreen = box.bottom > 0 && box.right > 0 && box.top < height && box.left < width;
19
- if (sized && onScreen) {
20
- count += 1;
11
+ return Array.from(doc.querySelectorAll(CANVAS_SELECTOR)).filter((canvas) => !isReticleUi(canvas) && isInViewport(canvas)).length;
12
+ }
13
+ export function positionAmongNamesakes(el, role, name) {
14
+ const same = name.length > 0
15
+ ? elementsMatching({ by: QueryBy.ROLE, value: role, name })
16
+ : elementsMatching({ by: QueryBy.ROLE, value: role }).filter((candidate) => 0 === getAccessibleName(candidate).length);
17
+ const at = same.indexOf(el);
18
+ return 1 < same.length && 0 <= at ? { nth: at, of: same.length } : null;
19
+ }
20
+ function positionInComponentAnchor(el, component, source, role) {
21
+ if (component === undefined && source === undefined) {
22
+ return null;
23
+ }
24
+ const same = elementsMatching({
25
+ by: QueryBy.COMPONENT,
26
+ value: component ?? '',
27
+ ...(component === undefined ? {} : { component }),
28
+ ...(source === undefined ? {} : { source }),
29
+ ...(role.length > 0 ? { role } : {}),
30
+ });
31
+ const at = same.indexOf(el);
32
+ return 1 < same.length && 0 <= at ? { nth: at, of: same.length } : null;
33
+ }
34
+ const CONTAINER_ROLES = new Set([
35
+ 'alert',
36
+ 'alertdialog',
37
+ 'dialog',
38
+ 'form',
39
+ 'region',
40
+ 'navigation',
41
+ 'menu',
42
+ 'menubar',
43
+ 'listbox',
44
+ 'list',
45
+ 'listitem',
46
+ 'tabpanel',
47
+ 'table',
48
+ 'grid',
49
+ 'row',
50
+ 'group',
51
+ 'toolbar',
52
+ 'tree',
53
+ 'search',
54
+ ]);
55
+ const WITHIN_MAX = 2;
56
+ function containerRoles(el) {
57
+ const out = [];
58
+ for (let a = el.parentElement; a !== null && out.length < WITHIN_MAX; a = a.parentElement) {
59
+ const role = getRole(a);
60
+ const landmarkNeedsName = 'form' === role || 'region' === role;
61
+ if (CONTAINER_ROLES.has(role) && (!landmarkNeedsName || getAccessibleName(a).length > 0)) {
62
+ out.push(role);
21
63
  }
22
64
  }
23
- return count;
65
+ return out.length > 0 ? out : undefined;
24
66
  }
67
+ const OWN_TEXT_MAX = 80;
25
68
  export function anchorOf(el) {
26
69
  const testid = readTestId(el) ?? undefined;
27
70
  const info = identifyComponent(el);
28
71
  const out = {};
29
72
  if (testid !== undefined) {
30
73
  out.testid = testid;
31
- const shared = elementsMatching({ by: QueryBy.TESTID, value: testid }).length;
32
- if (1 < shared) {
33
- out.testidOf = shared;
74
+ const shared = elementsMatching({ by: QueryBy.TESTID, value: testid });
75
+ if (1 < shared.length) {
76
+ out.testidOf = shared.length;
77
+ out.testidNth = shared.findIndex((match) => match === el);
34
78
  }
35
79
  }
36
80
  const component = info?.componentStack[0];
@@ -42,21 +86,35 @@ export function anchorOf(el) {
42
86
  }
43
87
  const role = getRole(el);
44
88
  const name = getAccessibleName(el);
89
+ const position = positionInComponentAnchor(el, out.component, out.source, role);
90
+ if (position !== null) {
91
+ out.componentNth = position.nth;
92
+ out.componentOf = position.of;
93
+ }
45
94
  if (role.length > 0) {
46
95
  out.role = role;
47
96
  }
48
97
  if (name.length > 0) {
49
98
  out.name = name;
99
+ const within = containerRoles(el);
100
+ if (within !== undefined) {
101
+ out.within = within;
102
+ }
103
+ }
104
+ else if (out.component !== undefined || out.source !== undefined) {
105
+ const words = ownWords(el);
106
+ if (0 < words.length && words.length <= OWN_TEXT_MAX) {
107
+ out.ownText = words;
108
+ }
50
109
  }
51
110
  if (isSensitiveField(el)) {
52
111
  out.secret = true;
53
112
  }
54
- if (role.length > 0 && name.length > 0) {
55
- const same = elementsMatching({ by: QueryBy.ROLE, value: role, name });
56
- const at = same.indexOf(el);
57
- if (1 < same.length && 0 <= at) {
58
- out.nth = at;
59
- out.of = same.length;
113
+ if (role.length > 0) {
114
+ const position = positionAmongNamesakes(el, role, name);
115
+ if (position !== null) {
116
+ out.nth = position.nth;
117
+ out.of = position.of;
60
118
  }
61
119
  }
62
120
  return out;
@@ -77,12 +135,27 @@ export const result = (ref, action, effect, settled, settleReason, anchor, warni
77
135
  if (anchor.testidOf !== undefined) {
78
136
  base.testidOf = anchor.testidOf;
79
137
  }
138
+ if (anchor.testidNth !== undefined) {
139
+ base.testidNth = anchor.testidNth;
140
+ }
80
141
  if (anchor.component !== undefined) {
81
142
  base.component = anchor.component;
82
143
  }
83
144
  if (anchor.source !== undefined) {
84
145
  base.source = anchor.source;
85
146
  }
147
+ if (anchor.componentNth !== undefined) {
148
+ base.componentNth = anchor.componentNth;
149
+ }
150
+ if (anchor.componentOf !== undefined) {
151
+ base.componentOf = anchor.componentOf;
152
+ }
153
+ if (anchor.ownText !== undefined) {
154
+ base.ownText = anchor.ownText;
155
+ }
156
+ if (anchor.within !== undefined) {
157
+ base.within = anchor.within;
158
+ }
86
159
  if (anchor.role !== undefined) {
87
160
  base.role = anchor.role;
88
161
  }
@@ -1,5 +1,5 @@
1
1
  import type { ActionTarget } from '../dom/realm.js';
2
- import { ClickButton } from '@reticlehq/core';
2
+ import { ClickButton, type BoxFraction } from '@reticlehq/core';
3
3
  import { type ModifierFlags } from './actions-press.js';
4
4
  interface ClickGeometry {
5
5
  occluded: boolean;
@@ -21,20 +21,31 @@ export declare function fireClickSequence(el: ActionTarget, hold?: {
21
21
  ms: number;
22
22
  sleep: (ms: number) => Promise<void>;
23
23
  now: () => number;
24
- }, detail?: number, input?: ClickInput): Promise<{
24
+ }, detail?: number, input?: ClickInput, point?: BoxFraction): Promise<{
25
25
  prevented: boolean;
26
26
  heldMs: number;
27
27
  }>;
28
+ export declare function scrollIntoViewNow(el: Element, options?: ScrollIntoViewOptions): void;
28
29
  export declare function isOffViewport(el: Element, rect: DOMRect): boolean;
29
30
  export declare function clickGeometry(el: ActionTarget): ClickGeometry;
30
- export declare function firePointer(el: Element, type: string, relatedTarget?: EventTarget | null): void;
31
- export declare function firePointerNonBubbling(el: Element, type: string, relatedTarget?: EventTarget | null): void;
32
- export declare function dragElement(source: ActionTarget, target: ActionTarget | null, data: unknown): Promise<boolean>;
31
+ export declare function firePointer(el: Element, type: string, relatedTarget?: EventTarget | null, coords?: MouseEventInit): void;
32
+ export declare function leavePreviousHover(next: Element): EventTarget;
33
+ export declare function firePointerNonBubbling(el: Element, type: string, relatedTarget?: EventTarget | null, coords?: MouseEventInit): void;
34
+ interface Point {
35
+ x: number;
36
+ y: number;
37
+ }
38
+ export declare function coordsAt(p: Point): MouseEventInit;
39
+ export declare function coordsIn(el: ActionTarget, at: BoxFraction | undefined): MouseEventInit;
40
+ export declare function dragElement(source: ActionTarget, target: ActionTarget | null, data: unknown, points?: {
41
+ at?: BoxFraction | undefined;
42
+ to?: BoxFraction | undefined;
43
+ }, input?: ClickInput): Promise<boolean>;
33
44
  export declare function fireTapSequence(el: ActionTarget, hold: {
34
45
  ms: number;
35
46
  sleep: (ms: number) => Promise<void>;
36
47
  now: () => number;
37
- } | undefined): Promise<{
48
+ } | undefined, point?: BoxFraction): Promise<{
38
49
  prevented: boolean;
39
50
  heldMs: number;
40
51
  }>;
@@ -4,7 +4,7 @@ import { getAccessibleName, getRole } from '../dom/a11y.js';
4
4
  import { hitTestOccluder } from '../dom/occlusion.js';
5
5
  import { nativeFrame } from '../timers/native/native-timers.js';
6
6
  import { asSyntheticInput } from './synthetic/synthetic-input.js';
7
- import { ClickButton, clickButtonFromArgs } from '@reticlehq/core';
7
+ import { ClickButton, clickButtonFromArgs, pointInBox } from '@reticlehq/core';
8
8
  import { pressModifiers } from './actions-press.js';
9
9
  export const NO_GEOMETRY = {
10
10
  occluded: false,
@@ -55,10 +55,10 @@ function fireAt(el, type, at, buttons, related, detail = 0, input = PLAIN_CLICK)
55
55
  ? pointerEventFor(el, type, { ...init, pointerId: 1, isPrimary: true })
56
56
  : mouseEventFor(el, type, init));
57
57
  }
58
- export async function fireClickSequence(el, hold, detail, input = PLAIN_CLICK) {
58
+ export async function fireClickSequence(el, hold, detail, input = PLAIN_CLICK, point) {
59
59
  const doc = el.ownerDocument;
60
60
  const from = doc.activeElement ?? doc.body;
61
- const at = centreOf(el);
61
+ const at = pointOf(el, point);
62
62
  const d = detail ?? 0;
63
63
  const held = BUTTON_CODES[input.button].bit;
64
64
  const isLeft = ClickButton.LEFT === input.button;
@@ -88,6 +88,9 @@ export async function fireClickSequence(el, hold, detail, input = PLAIN_CLICK) {
88
88
  function isMeasurable(rect) {
89
89
  return rect.width > 0 || rect.height > 0;
90
90
  }
91
+ export function scrollIntoViewNow(el, options = {}) {
92
+ el.scrollIntoView({ ...options, behavior: 'instant' });
93
+ }
91
94
  export function isOffViewport(el, rect) {
92
95
  const win = el.ownerDocument.defaultView;
93
96
  if (null === win) {
@@ -116,30 +119,70 @@ function hasNoSize(el) {
116
119
  return false;
117
120
  }
118
121
  const rects = Array.from(el.getClientRects());
119
- return rects.length > 0 && rects.every((r) => 0 === r.width || 0 === r.height);
122
+ if (0 === rects.length) {
123
+ return isMeasurable(el.ownerDocument.documentElement.getBoundingClientRect());
124
+ }
125
+ return rects.every((r) => 0 === r.width || 0 === r.height);
126
+ }
127
+ function sizedLabelOf(el) {
128
+ const labels = 'labels' in el ? el.labels : null;
129
+ for (const label of labels ?? []) {
130
+ const box = label.getBoundingClientRect();
131
+ if (0 < box.width && 0 < box.height) {
132
+ return label;
133
+ }
134
+ }
135
+ return null;
120
136
  }
121
137
  export function clickGeometry(el) {
122
138
  if (typeof el.getBoundingClientRect !== 'function') {
123
139
  return NO_GEOMETRY;
124
140
  }
125
141
  let rect = el.getBoundingClientRect();
142
+ const sizeless = isMeasurable(rect) ? 0 === rect.width || 0 === rect.height : hasNoSize(el);
143
+ if (sizeless) {
144
+ const label = sizedLabelOf(el);
145
+ if (label !== null) {
146
+ return clickGeometry(label);
147
+ }
148
+ return { ...NO_GEOMETRY, noSize: true };
149
+ }
126
150
  if (!isMeasurable(rect)) {
127
- return hasNoSize(el) ? { ...NO_GEOMETRY, noSize: true } : NO_GEOMETRY;
151
+ return NO_GEOMETRY;
128
152
  }
129
153
  let scrolledIntoView = false;
130
154
  if (isOffViewport(el, rect) && 'function' === typeof el.scrollIntoView) {
131
- el.scrollIntoView({ block: 'center', inline: 'center' });
155
+ scrollIntoViewNow(el, { block: 'center', inline: 'center' });
132
156
  scrolledIntoView = true;
133
157
  rect = el.getBoundingClientRect();
134
158
  }
135
159
  const stillOff = scrolledIntoView && isOffViewport(el, rect);
136
160
  return { ...hitTest(el, rect), scrolledIntoView, ...(stillOff ? { offViewport: true } : {}) };
137
161
  }
138
- export function firePointer(el, type, relatedTarget = null) {
139
- asSyntheticInput(() => el.dispatchEvent(pointerEventFor(el, type, { bubbles: true, cancelable: true, relatedTarget })));
162
+ export function firePointer(el, type, relatedTarget = null, coords = {}) {
163
+ asSyntheticInput(() => el.dispatchEvent(pointerEventFor(el, type, { bubbles: true, cancelable: true, relatedTarget, ...coords })));
140
164
  }
141
- export function firePointerNonBubbling(el, type, relatedTarget = null) {
142
- asSyntheticInput(() => el.dispatchEvent(pointerEventFor(el, type, { bubbles: false, cancelable: true, relatedTarget })));
165
+ const hoveredIn = new WeakMap();
166
+ export function leavePreviousHover(next) {
167
+ const doc = next.ownerDocument;
168
+ const prev = hoveredIn.get(doc);
169
+ hoveredIn.set(doc, next);
170
+ if (prev === undefined || !prev.isConnected) {
171
+ return doc.activeElement ?? doc.body;
172
+ }
173
+ if (prev === next) {
174
+ return prev;
175
+ }
176
+ firePointer(prev, 'pointerout', next);
177
+ prev.dispatchEvent(mouseEventFor(prev, 'mouseout', { bubbles: true, relatedTarget: next }));
178
+ for (let el = prev; el !== null && !el.contains(next); el = el.parentElement) {
179
+ firePointerNonBubbling(el, 'pointerleave', next);
180
+ el.dispatchEvent(mouseEventFor(el, 'mouseleave', { relatedTarget: next }));
181
+ }
182
+ return prev;
183
+ }
184
+ export function firePointerNonBubbling(el, type, relatedTarget = null, coords = {}) {
185
+ asSyntheticInput(() => el.dispatchEvent(pointerEventFor(el, type, { bubbles: false, cancelable: true, relatedTarget, ...coords })));
143
186
  }
144
187
  function makeDataTransfer(data) {
145
188
  if (typeof DataTransfer !== 'function') {
@@ -162,18 +205,31 @@ function centreOf(el) {
162
205
  const box = el.getBoundingClientRect();
163
206
  return { x: box.left + box.width / 2, y: box.top + box.height / 2 };
164
207
  }
208
+ function pointOf(el, at) {
209
+ if (at === undefined) {
210
+ return centreOf(el);
211
+ }
212
+ const box = el.getBoundingClientRect();
213
+ return pointInBox({ x: box.left, y: box.top, width: box.width, height: box.height }, at);
214
+ }
215
+ export function coordsAt(p) {
216
+ return { clientX: p.x, clientY: p.y, screenX: p.x, screenY: p.y };
217
+ }
218
+ export function coordsIn(el, at) {
219
+ return at === undefined ? {} : coordsAt(pointOf(el, at));
220
+ }
165
221
  function lerp(a, b, t) {
166
222
  return { x: Math.round(a.x + (b.x - a.x) * t), y: Math.round(a.y + (b.y - a.y) * t) };
167
223
  }
168
- export async function dragElement(source, target, data) {
224
+ export async function dragElement(source, target, data, points = {}, input = PLAIN_CLICK) {
169
225
  const dest = target ?? source;
170
- const from = centreOf(source);
171
- const to = centreOf(dest);
226
+ const from = pointOf(source, points.at);
227
+ const to = pointOf(dest, points.to);
172
228
  const sourceRect = source.getBoundingClientRect();
173
229
  const destRect = target !== null ? dest.getBoundingClientRect() : null;
174
230
  const crosses = (rect, p) => p.x >= rect.left && p.x <= rect.right && p.y >= rect.top && p.y <= rect.bottom;
175
- fireAt(source, 'pointerdown', from, BUTTON_HELD);
176
- fireAt(source, 'mousedown', from, BUTTON_HELD);
231
+ fireAt(source, 'pointerdown', from, BUTTON_HELD, undefined, 0, input);
232
+ fireAt(source, 'mousedown', from, BUTTON_HELD, undefined, 0, input);
177
233
  await nativeFrame();
178
234
  let leftSource = false;
179
235
  let enteredDest = false;
@@ -181,30 +237,30 @@ export async function dragElement(source, target, data) {
181
237
  const at = lerp(from, to, step / DRAG_STEPS);
182
238
  if (!leftSource && !crosses(sourceRect, at)) {
183
239
  leftSource = true;
184
- fireAt(source, 'pointerout', at, BUTTON_HELD, dest);
185
- fireAt(source, 'mouseout', at, BUTTON_HELD, dest);
186
- fireAt(source, 'pointerleave', at, BUTTON_HELD, dest);
187
- fireAt(source, 'mouseleave', at, BUTTON_HELD, dest);
240
+ fireAt(source, 'pointerout', at, BUTTON_HELD, dest, 0, input);
241
+ fireAt(source, 'mouseout', at, BUTTON_HELD, dest, 0, input);
242
+ fireAt(source, 'pointerleave', at, BUTTON_HELD, dest, 0, input);
243
+ fireAt(source, 'mouseleave', at, BUTTON_HELD, dest, 0, input);
188
244
  }
189
245
  if (leftSource && !enteredDest && destRect !== null && crosses(destRect, at)) {
190
246
  enteredDest = true;
191
- fireAt(dest, 'pointerover', at, BUTTON_HELD, source);
192
- fireAt(dest, 'mouseover', at, BUTTON_HELD, source);
193
- fireAt(dest, 'pointerenter', at, BUTTON_HELD, source);
194
- fireAt(dest, 'mouseenter', at, BUTTON_HELD, source);
247
+ fireAt(dest, 'pointerover', at, BUTTON_HELD, source, 0, input);
248
+ fireAt(dest, 'mouseover', at, BUTTON_HELD, source, 0, input);
249
+ fireAt(dest, 'pointerenter', at, BUTTON_HELD, source, 0, input);
250
+ fireAt(dest, 'mouseenter', at, BUTTON_HELD, source, 0, input);
195
251
  }
196
- fireAt(dest, 'pointermove', at, BUTTON_HELD);
197
- fireAt(dest, 'mousemove', at, BUTTON_HELD);
252
+ fireAt(dest, 'pointermove', at, BUTTON_HELD, undefined, 0, input);
253
+ fireAt(dest, 'mousemove', at, BUTTON_HELD, undefined, 0, input);
198
254
  await nativeFrame();
199
255
  }
200
256
  if (!enteredDest && destRect !== null) {
201
- fireAt(dest, 'pointerover', to, BUTTON_HELD, source);
202
- fireAt(dest, 'mouseover', to, BUTTON_HELD, source);
203
- fireAt(dest, 'pointerenter', to, BUTTON_HELD, source);
204
- fireAt(dest, 'mouseenter', to, BUTTON_HELD, source);
257
+ fireAt(dest, 'pointerover', to, BUTTON_HELD, source, 0, input);
258
+ fireAt(dest, 'mouseover', to, BUTTON_HELD, source, 0, input);
259
+ fireAt(dest, 'pointerenter', to, BUTTON_HELD, source, 0, input);
260
+ fireAt(dest, 'mouseenter', to, BUTTON_HELD, source, 0, input);
205
261
  }
206
- fireAt(dest, 'pointerup', to, BUTTON_RELEASED);
207
- fireAt(dest, 'mouseup', to, BUTTON_RELEASED);
262
+ fireAt(dest, 'pointerup', to, BUTTON_RELEASED, undefined, 0, input);
263
+ fireAt(dest, 'mouseup', to, BUTTON_RELEASED, undefined, 0, input);
208
264
  let dropPrevented = false;
209
265
  if ('function' === typeof DragEvent) {
210
266
  const dataTransfer = makeDataTransfer(data);
@@ -222,9 +278,10 @@ export async function dragElement(source, target, data) {
222
278
  }
223
279
  return dropPrevented;
224
280
  }
225
- export async function fireTapSequence(el, hold) {
226
- const touches = touchListFor(el);
227
- firePointerTouch(el, 'pointerdown');
281
+ export async function fireTapSequence(el, hold, point) {
282
+ const touches = touchListFor(el, point);
283
+ const coords = coordsIn(el, point);
284
+ firePointerTouch(el, 'pointerdown', coords);
228
285
  asSyntheticInput(() => el.dispatchEvent(makeTouchEvent('touchstart', touches)));
229
286
  if (el.tabIndex >= 0 && 'function' === typeof el.focus) {
230
287
  el.focus();
@@ -236,11 +293,11 @@ export async function fireTapSequence(el, hold) {
236
293
  heldMs = hold.now() - started;
237
294
  }
238
295
  asSyntheticInput(() => el.dispatchEvent(makeTouchEvent('touchend', [])));
239
- firePointerTouch(el, 'pointerup');
240
- const clicked = asSyntheticInput(() => el.dispatchEvent(mouseEventFor(el, 'click', { bubbles: true, cancelable: true })));
296
+ firePointerTouch(el, 'pointerup', coords);
297
+ const clicked = asSyntheticInput(() => el.dispatchEvent(mouseEventFor(el, 'click', { bubbles: true, cancelable: true, ...coords })));
241
298
  return { prevented: !clicked, heldMs };
242
299
  }
243
- function firePointerTouch(el, type) {
300
+ function firePointerTouch(el, type, coords) {
244
301
  asSyntheticInput(() => el.dispatchEvent(pointerEventFor(el, type, {
245
302
  bubbles: true,
246
303
  cancelable: true,
@@ -248,22 +305,16 @@ function firePointerTouch(el, type) {
248
305
  isPrimary: true,
249
306
  width: 23,
250
307
  height: 23,
308
+ ...coords,
251
309
  })));
252
310
  }
253
- function touchListFor(el) {
311
+ function touchListFor(el, point) {
254
312
  if ('function' !== typeof Touch || 'function' !== typeof el.getBoundingClientRect) {
255
313
  return [];
256
314
  }
257
- const rect = el.getBoundingClientRect();
315
+ const at = pointOf(el, point);
258
316
  try {
259
- return [
260
- new Touch({
261
- identifier: 0,
262
- target: el,
263
- clientX: rect.left + rect.width / 2,
264
- clientY: rect.top + rect.height / 2,
265
- }),
266
- ];
317
+ return [new Touch({ identifier: 0, target: el, clientX: at.x, clientY: at.y })];
267
318
  }
268
319
  catch {
269
320
  return [];
@@ -19,3 +19,13 @@ export declare function keyboardEvent(type: string, init: KeyboardEventInit & {
19
19
  export declare function holdKey(el: ActionTarget, key: string, code: string, mods: ModifierFlags, ms: number): Promise<void>;
20
20
  export declare function pressCombo(el: ActionTarget, keys: readonly string[], mods: ModifierFlags, holdMs: number): Promise<boolean>;
21
21
  export declare function closeModalOnEscape(el: ActionTarget, key: string, keydownProceeded: boolean): void;
22
+ export declare const SPACE_KEY = " ";
23
+ interface Held {
24
+ metaKey: boolean;
25
+ ctrlKey: boolean;
26
+ altKey: boolean;
27
+ shiftKey: boolean;
28
+ }
29
+ export declare function runKeydownDefault(el: Element, key: string, held: Held): void;
30
+ export declare function runKeyupDefault(el: Element, key: string, held: Held): void;
31
+ export {};