@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.
- package/dist/actions/action-report.d.ts +14 -0
- package/dist/actions/action-report.js +95 -22
- package/dist/actions/actions-dom.d.ts +17 -6
- package/dist/actions/actions-dom.js +98 -47
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +146 -2
- package/dist/actions/actions.js +64 -24
- package/dist/actions/insert-text.d.ts +3 -0
- package/dist/actions/insert-text.js +90 -0
- package/dist/actions/scroll.d.ts +1 -1
- package/dist/actions/scroll.js +25 -1
- package/dist/actions/value-input.d.ts +2 -0
- package/dist/actions/value-input.js +30 -9
- package/dist/commands/commands.js +85 -9
- package/dist/connect-options.d.ts +11 -1
- package/dist/connect-options.js +27 -1
- package/dist/dom/a11y.d.ts +1 -1
- package/dist/dom/a11y.js +11 -10
- package/dist/dom/chart-geometry.d.ts +7 -0
- package/dist/dom/chart-geometry.js +39 -0
- package/dist/dom/occlusion.js +21 -0
- package/dist/dom/query-finders.d.ts +1 -1
- package/dist/dom/query-finders.js +10 -7
- package/dist/dom/query.d.ts +6 -2
- package/dist/dom/query.js +165 -54
- package/dist/dom/scope-roots.d.ts +8 -0
- package/dist/dom/scope-roots.js +44 -0
- package/dist/dom/snapshot.d.ts +3 -0
- package/dist/dom/snapshot.js +9 -2
- package/dist/dom/text-match.js +6 -4
- package/dist/dom/theme.js +6 -7
- package/dist/dom/visibility.d.ts +3 -0
- package/dist/dom/visibility.js +125 -10
- package/dist/observers/canvas.d.ts +1 -0
- package/dist/observers/canvas.js +10 -1
- package/dist/observers/console.js +46 -35
- package/dist/observers/dom.js +28 -4
- package/dist/observers/net-detail/resource-timing.d.ts +12 -0
- package/dist/observers/net-detail/resource-timing.js +69 -0
- package/dist/observers/network.js +13 -44
- package/dist/observers/perf.js +15 -1
- package/dist/presenter/chrome/presenter-verbs.js +1 -0
- package/dist/presenter/presenter-config.d.ts +1 -0
- package/dist/presenter/presenter-config.js +1 -0
- package/dist/presenter/presenter-shell.d.ts +1 -0
- package/dist/presenter/presenter-shell.js +6 -3
- package/dist/presenter/tour/tour.js +4 -0
- package/dist/recorder/recorder.js +82 -39
- package/dist/reticle-inject.js +618 -618
- package/dist/reticle.d.ts +3 -19
- package/dist/reticle.js +76 -110
- package/dist/review/annotator.js +4 -0
- package/dist/security/serialization.js +35 -0
- package/dist/session-continuity.d.ts +1 -0
- package/dist/session-continuity.js +18 -0
- package/dist/storage-keys.js +2 -1
- package/dist/timers/native/native-timers.js +21 -4
- package/dist/transport/bridge-url.d.ts +8 -0
- package/dist/transport/bridge-url.js +52 -0
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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 })
|
|
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
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
out.
|
|
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
|
|
32
|
-
export declare function
|
|
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 =
|
|
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
|
-
|
|
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
|
|
151
|
+
return NO_GEOMETRY;
|
|
128
152
|
}
|
|
129
153
|
let scrolledIntoView = false;
|
|
130
154
|
if (isOffViewport(el, rect) && 'function' === typeof el.scrollIntoView) {
|
|
131
|
-
el
|
|
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
|
-
|
|
142
|
-
|
|
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 =
|
|
171
|
-
const to =
|
|
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
|
-
|
|
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
|
|
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 {};
|