screenci 0.0.20 → 0.0.22
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/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -19
- package/dist/cli.js.map +1 -1
- package/dist/e2e/instrument.e2e.js +98 -68
- package/dist/e2e/instrument.e2e.js.map +1 -1
- package/dist/src/autoZoom.d.ts +34 -15
- package/dist/src/autoZoom.d.ts.map +1 -1
- package/dist/src/autoZoom.js +100 -69
- package/dist/src/autoZoom.js.map +1 -1
- package/dist/src/changeFocus.d.ts +129 -0
- package/dist/src/changeFocus.d.ts.map +1 -0
- package/dist/src/changeFocus.js +885 -0
- package/dist/src/changeFocus.js.map +1 -0
- package/dist/src/defaults.d.ts +2 -17
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +8 -16
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/easing.d.ts +6 -0
- package/dist/src/easing.d.ts.map +1 -0
- package/dist/src/easing.js +30 -0
- package/dist/src/easing.js.map +1 -0
- package/dist/src/errors.d.ts +10 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +26 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/events.d.ts +35 -12
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +137 -31
- package/dist/src/events.js.map +1 -1
- package/dist/src/instrument.d.ts +0 -6
- package/dist/src/instrument.d.ts.map +1 -1
- package/dist/src/instrument.js +382 -812
- package/dist/src/instrument.js.map +1 -1
- package/dist/src/mouse.d.ts +128 -0
- package/dist/src/mouse.d.ts.map +1 -0
- package/dist/src/mouse.js +268 -0
- package/dist/src/mouse.js.map +1 -0
- package/dist/src/recordingData.d.ts +30 -4
- package/dist/src/recordingData.d.ts.map +1 -1
- package/dist/src/types.d.ts +4 -7
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/zoom.d.ts +69 -0
- package/dist/src/zoom.d.ts.map +1 -0
- package/dist/src/zoom.js +90 -0
- package/dist/src/zoom.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/src/scroll.d.ts +0 -47
- package/dist/src/scroll.d.ts.map +0 -1
- package/dist/src/scroll.js +0 -347
- package/dist/src/scroll.js.map +0 -1
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { evaluateEasingAtT } from './easing.js';
|
|
2
|
+
import { DEFAULT_ZOOM_OPTIONS } from './defaults.js';
|
|
3
|
+
import { getMousePosition, performMouseMove } from './mouse.js';
|
|
4
|
+
import { getAutoZoomState, setCurrentZoomViewport } from './autoZoom.js';
|
|
5
|
+
import { buildZoomEvent, resolveAutoZoomOptions, resolveZoomTarget, } from './zoom.js';
|
|
6
|
+
const POSITION_EPSILON_PX = 0.5;
|
|
7
|
+
const CURSOR_TRIGGER_EDGE_THRESHOLD = 0.3;
|
|
8
|
+
const CURSOR_TRIGGER_MAX_PROGRESS = 0.6;
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
function clamp(value, min, max) {
|
|
13
|
+
return Math.min(max, Math.max(min, value));
|
|
14
|
+
}
|
|
15
|
+
function positionsDiffer(start, target) {
|
|
16
|
+
return Math.abs(target - start) > POSITION_EPSILON_PX;
|
|
17
|
+
}
|
|
18
|
+
function clampToRange(value, range) {
|
|
19
|
+
return clamp(value, range.min, range.max);
|
|
20
|
+
}
|
|
21
|
+
function shiftRect(rect, deltaX, deltaY) {
|
|
22
|
+
return {
|
|
23
|
+
x: rect.x + deltaX,
|
|
24
|
+
y: rect.y + deltaY,
|
|
25
|
+
width: rect.width,
|
|
26
|
+
height: rect.height,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function resolveFixedFocusViewportSize(viewport, amount) {
|
|
30
|
+
return {
|
|
31
|
+
width: viewport.width * amount,
|
|
32
|
+
height: viewport.height * amount,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function resolveIdealFocusOriginForAxis(params) {
|
|
36
|
+
const { rectStart, rectSize, focusSize, centering } = params;
|
|
37
|
+
if (rectSize <= focusSize) {
|
|
38
|
+
const slack = focusSize - rectSize;
|
|
39
|
+
const idealRectOffset = (slack * centering) / 2;
|
|
40
|
+
return rectStart - idealRectOffset;
|
|
41
|
+
}
|
|
42
|
+
return rectStart + rectSize / 2 - focusSize / 2;
|
|
43
|
+
}
|
|
44
|
+
export function resolveIdealFocusOrigin(rect, focusViewport, centering) {
|
|
45
|
+
return {
|
|
46
|
+
x: resolveIdealFocusOriginForAxis({
|
|
47
|
+
rectStart: rect.x,
|
|
48
|
+
rectSize: rect.width,
|
|
49
|
+
focusSize: focusViewport.width,
|
|
50
|
+
centering,
|
|
51
|
+
}),
|
|
52
|
+
y: resolveIdealFocusOriginForAxis({
|
|
53
|
+
rectStart: rect.y,
|
|
54
|
+
rectSize: rect.height,
|
|
55
|
+
focusSize: focusViewport.height,
|
|
56
|
+
centering,
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function resolveOptimalOffset(ideal, actual) {
|
|
61
|
+
return {
|
|
62
|
+
x: ideal.x - actual.x,
|
|
63
|
+
y: ideal.y - actual.y,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function resolveNearestViewportSide(point, viewportSize) {
|
|
67
|
+
const distances = [
|
|
68
|
+
{ side: 'left', distance: point.x },
|
|
69
|
+
{ side: 'right', distance: viewportSize.width - point.x },
|
|
70
|
+
{ side: 'top', distance: point.y },
|
|
71
|
+
{ side: 'bottom', distance: viewportSize.height - point.y },
|
|
72
|
+
];
|
|
73
|
+
return distances.reduce((nearest, current) => current.distance < nearest.distance ? current : nearest).side;
|
|
74
|
+
}
|
|
75
|
+
function resolveCursorTriggerCoordinate(params) {
|
|
76
|
+
const { side, viewportSize, target, threshold } = params;
|
|
77
|
+
switch (side) {
|
|
78
|
+
case 'left':
|
|
79
|
+
return Math.max(target.x, viewportSize.width * threshold);
|
|
80
|
+
case 'right':
|
|
81
|
+
return Math.min(target.x, viewportSize.width * (1 - threshold));
|
|
82
|
+
case 'top':
|
|
83
|
+
return Math.max(target.y, viewportSize.height * threshold);
|
|
84
|
+
case 'bottom':
|
|
85
|
+
return Math.min(target.y, viewportSize.height * (1 - threshold));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function hasReachedCursorTrigger(params) {
|
|
89
|
+
const { side, point, triggerCoordinate } = params;
|
|
90
|
+
switch (side) {
|
|
91
|
+
case 'left':
|
|
92
|
+
return point.x <= triggerCoordinate;
|
|
93
|
+
case 'right':
|
|
94
|
+
return point.x >= triggerCoordinate;
|
|
95
|
+
case 'top':
|
|
96
|
+
return point.y <= triggerCoordinate;
|
|
97
|
+
case 'bottom':
|
|
98
|
+
return point.y >= triggerCoordinate;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function resolveScrollAndZoomTimingPlan(params) {
|
|
102
|
+
const { viewportSize, target, startViewportPos, duration, easing, cursorTriggerEdgeThreshold, cursorTriggerMaxProgress, } = params;
|
|
103
|
+
if (duration <= 0) {
|
|
104
|
+
return { startDelay: 0, duration: Math.max(0, duration) };
|
|
105
|
+
}
|
|
106
|
+
const latestStartDelay = duration * cursorTriggerMaxProgress;
|
|
107
|
+
const side = resolveNearestViewportSide(target, viewportSize);
|
|
108
|
+
const triggerCoordinate = resolveCursorTriggerCoordinate({
|
|
109
|
+
side,
|
|
110
|
+
viewportSize,
|
|
111
|
+
target,
|
|
112
|
+
threshold: cursorTriggerEdgeThreshold,
|
|
113
|
+
});
|
|
114
|
+
if (hasReachedCursorTrigger({
|
|
115
|
+
side,
|
|
116
|
+
point: startViewportPos,
|
|
117
|
+
triggerCoordinate,
|
|
118
|
+
})) {
|
|
119
|
+
return { startDelay: 0, duration };
|
|
120
|
+
}
|
|
121
|
+
const frameMs = 1000 / 60;
|
|
122
|
+
const steps = Math.max(1, Math.floor(duration / frameMs));
|
|
123
|
+
for (let step = 1; step <= steps; step += 1) {
|
|
124
|
+
const t = step / steps;
|
|
125
|
+
const easedT = evaluateEasingAtT(t, easing);
|
|
126
|
+
const point = {
|
|
127
|
+
x: startViewportPos.x + (target.x - startViewportPos.x) * easedT,
|
|
128
|
+
y: startViewportPos.y + (target.y - startViewportPos.y) * easedT,
|
|
129
|
+
};
|
|
130
|
+
if (hasReachedCursorTrigger({ side, point, triggerCoordinate })) {
|
|
131
|
+
const startDelay = Math.min(duration * t, latestStartDelay);
|
|
132
|
+
return {
|
|
133
|
+
startDelay,
|
|
134
|
+
duration: Math.max(0, duration - startDelay),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
startDelay: latestStartDelay,
|
|
140
|
+
duration: Math.max(0, duration - latestStartDelay),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function resolveTargetRectStartForAxis(params) {
|
|
144
|
+
const { containerSize, rectSize, focusSize, centering } = params;
|
|
145
|
+
const focusWindowStart = Math.max(0, (containerSize - focusSize) / 2);
|
|
146
|
+
if (rectSize <= focusSize) {
|
|
147
|
+
// centering = 0 keeps the rect edge-aligned; centering = 1 centers it.
|
|
148
|
+
const edgeAlignedStart = focusWindowStart;
|
|
149
|
+
const centeredStart = focusWindowStart + (focusSize - rectSize) / 2;
|
|
150
|
+
return edgeAlignedStart + (centeredStart - edgeAlignedStart) * centering;
|
|
151
|
+
}
|
|
152
|
+
return focusWindowStart + focusSize / 2 - rectSize / 2;
|
|
153
|
+
}
|
|
154
|
+
export function resolveTargetRectPosition(params) {
|
|
155
|
+
const { containerSize, rect, amount, centering } = params;
|
|
156
|
+
const targetViewport = resolveFixedFocusViewportSize(containerSize, amount);
|
|
157
|
+
return {
|
|
158
|
+
x: resolveTargetRectStartForAxis({
|
|
159
|
+
containerSize: containerSize.width,
|
|
160
|
+
rectSize: rect.width,
|
|
161
|
+
focusSize: targetViewport.width,
|
|
162
|
+
centering,
|
|
163
|
+
}),
|
|
164
|
+
y: resolveTargetRectStartForAxis({
|
|
165
|
+
containerSize: containerSize.height,
|
|
166
|
+
rectSize: rect.height,
|
|
167
|
+
focusSize: targetViewport.height,
|
|
168
|
+
centering,
|
|
169
|
+
}),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function captureFocusSnapshot(locator) {
|
|
173
|
+
return locator.evaluate((element) => {
|
|
174
|
+
const doc = element.ownerDocument;
|
|
175
|
+
const win = doc.defaultView;
|
|
176
|
+
if (!win) {
|
|
177
|
+
throw new Error('[screenci] Unable to resolve window while capturing focus snapshot.');
|
|
178
|
+
}
|
|
179
|
+
const isScrollable = (node) => {
|
|
180
|
+
if (!node ||
|
|
181
|
+
typeof node !== 'object' ||
|
|
182
|
+
!('getBoundingClientRect' in node) ||
|
|
183
|
+
!('clientHeight' in node) ||
|
|
184
|
+
!('clientWidth' in node) ||
|
|
185
|
+
!('scrollHeight' in node) ||
|
|
186
|
+
!('scrollWidth' in node) ||
|
|
187
|
+
!('scrollTop' in node) ||
|
|
188
|
+
!('scrollLeft' in node)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const el = node;
|
|
192
|
+
const style = win.getComputedStyle(node);
|
|
193
|
+
return (((style.overflowY === 'auto' ||
|
|
194
|
+
style.overflowY === 'scroll' ||
|
|
195
|
+
style.overflowY === 'overlay') &&
|
|
196
|
+
el.scrollHeight > el.clientHeight) ||
|
|
197
|
+
((style.overflowX === 'auto' ||
|
|
198
|
+
style.overflowX === 'scroll' ||
|
|
199
|
+
style.overflowX === 'overlay') &&
|
|
200
|
+
el.scrollWidth > el.clientWidth));
|
|
201
|
+
};
|
|
202
|
+
const rect = element.getBoundingClientRect();
|
|
203
|
+
const viewportHeight = win.innerHeight || doc.documentElement.clientHeight;
|
|
204
|
+
const viewportWidth = win.innerWidth || doc.documentElement.clientWidth;
|
|
205
|
+
const ancestors = [];
|
|
206
|
+
for (let current = element.parentElement; current; current = current.parentElement) {
|
|
207
|
+
if (!isScrollable(current) ||
|
|
208
|
+
current === doc.documentElement ||
|
|
209
|
+
current === doc.body) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const containerRect = current.getBoundingClientRect();
|
|
213
|
+
ancestors.push({
|
|
214
|
+
clientHeight: current.clientHeight,
|
|
215
|
+
clientWidth: current.clientWidth,
|
|
216
|
+
scrollHeight: current.scrollHeight,
|
|
217
|
+
scrollWidth: current.scrollWidth,
|
|
218
|
+
scrollTop: current.scrollTop,
|
|
219
|
+
scrollLeft: current.scrollLeft,
|
|
220
|
+
rect: {
|
|
221
|
+
top: containerRect.top,
|
|
222
|
+
left: containerRect.left,
|
|
223
|
+
width: containerRect.width,
|
|
224
|
+
height: containerRect.height,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
locatorRect: {
|
|
230
|
+
x: rect.left,
|
|
231
|
+
y: rect.top,
|
|
232
|
+
width: rect.width,
|
|
233
|
+
height: rect.height,
|
|
234
|
+
},
|
|
235
|
+
viewportSize: {
|
|
236
|
+
width: viewportWidth,
|
|
237
|
+
height: viewportHeight,
|
|
238
|
+
},
|
|
239
|
+
page: {
|
|
240
|
+
scrollY: win.scrollY,
|
|
241
|
+
scrollX: win.scrollX,
|
|
242
|
+
scrollHeight: Math.max(doc.documentElement.scrollHeight, doc.body?.scrollHeight ?? 0),
|
|
243
|
+
scrollWidth: Math.max(doc.documentElement.scrollWidth, doc.body?.scrollWidth ?? 0),
|
|
244
|
+
},
|
|
245
|
+
ancestors,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
function resolveMinimalVisibleScrollTargetForAxis(params) {
|
|
250
|
+
const { scrollStart, rectStart, rectSize, containerSize, scrollSize } = params;
|
|
251
|
+
const maxScroll = Math.max(0, scrollSize - containerSize);
|
|
252
|
+
const rectEnd = rectStart + rectSize;
|
|
253
|
+
if (rectSize <= containerSize) {
|
|
254
|
+
if (rectStart < 0) {
|
|
255
|
+
return clamp(scrollStart + rectStart, 0, maxScroll);
|
|
256
|
+
}
|
|
257
|
+
if (rectEnd > containerSize) {
|
|
258
|
+
return clamp(scrollStart + (rectEnd - containerSize), 0, maxScroll);
|
|
259
|
+
}
|
|
260
|
+
return scrollStart;
|
|
261
|
+
}
|
|
262
|
+
if (rectStart > 0) {
|
|
263
|
+
return clamp(scrollStart + rectStart, 0, maxScroll);
|
|
264
|
+
}
|
|
265
|
+
if (rectEnd < containerSize) {
|
|
266
|
+
return clamp(scrollStart + (rectEnd - containerSize), 0, maxScroll);
|
|
267
|
+
}
|
|
268
|
+
return scrollStart;
|
|
269
|
+
}
|
|
270
|
+
function resolveVisibleScrollTargetRangeForAxis(params) {
|
|
271
|
+
const { scrollStart, rectStart, rectSize, containerSize, scrollSize } = params;
|
|
272
|
+
const maxScroll = Math.max(0, scrollSize - containerSize);
|
|
273
|
+
const deltaAtStartEdge = rectStart;
|
|
274
|
+
const deltaAtEndEdge = rectStart + rectSize - containerSize;
|
|
275
|
+
return {
|
|
276
|
+
min: clamp(scrollStart + Math.min(deltaAtStartEdge, deltaAtEndEdge), 0, maxScroll),
|
|
277
|
+
max: clamp(scrollStart + Math.max(deltaAtStartEdge, deltaAtEndEdge), 0, maxScroll),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function projectRectFromAncestorPlans(snapshot, plans) {
|
|
281
|
+
const accumulatedDelta = plans.reduce((delta, plan, index) => ({
|
|
282
|
+
x: delta.x + (plan.targetLeft - snapshot.ancestors[index].scrollLeft),
|
|
283
|
+
y: delta.y + (plan.targetTop - snapshot.ancestors[index].scrollTop),
|
|
284
|
+
}), { x: 0, y: 0 });
|
|
285
|
+
return shiftRect(snapshot.locatorRect, -accumulatedDelta.x, -accumulatedDelta.y);
|
|
286
|
+
}
|
|
287
|
+
function resolveProjectedRectAcceptableRangeForAxis(params) {
|
|
288
|
+
const { pageScrollStart, pageScrollMax, viewportSize, targetViewportSize, targetRectStartInViewport, } = params;
|
|
289
|
+
const maxZoomOrigin = Math.max(0, viewportSize - targetViewportSize);
|
|
290
|
+
return {
|
|
291
|
+
min: targetRectStartInViewport - pageScrollStart,
|
|
292
|
+
max: targetRectStartInViewport +
|
|
293
|
+
maxZoomOrigin +
|
|
294
|
+
(pageScrollMax - pageScrollStart),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function refineAncestorPlansForProjectedRectRange(params) {
|
|
298
|
+
const nextPlans = params.plans.map((plan) => ({ ...plan }));
|
|
299
|
+
for (let sweep = 0; sweep < params.snapshot.ancestors.length; sweep += 1) {
|
|
300
|
+
const projectedRect = projectRectFromAncestorPlans(params.snapshot, nextPlans);
|
|
301
|
+
let remainingScrollLeft = projectedRect.x -
|
|
302
|
+
clampToRange(projectedRect.x, params.projectedRectRangeX);
|
|
303
|
+
let remainingScrollTop = projectedRect.y -
|
|
304
|
+
clampToRange(projectedRect.y, params.projectedRectRangeY);
|
|
305
|
+
if (Math.round(remainingScrollLeft) === 0 &&
|
|
306
|
+
Math.round(remainingScrollTop) === 0) {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
let changed = false;
|
|
310
|
+
for (let index = params.snapshot.ancestors.length - 1; index >= 0; index -= 1) {
|
|
311
|
+
const ancestor = params.snapshot.ancestors[index];
|
|
312
|
+
const accumulatedDeltaBeforeAncestor = nextPlans.slice(0, index).reduce((delta, plan, planIndex) => ({
|
|
313
|
+
x: delta.x +
|
|
314
|
+
(plan.targetLeft -
|
|
315
|
+
params.snapshot.ancestors[planIndex].scrollLeft),
|
|
316
|
+
y: delta.y +
|
|
317
|
+
(plan.targetTop - params.snapshot.ancestors[planIndex].scrollTop),
|
|
318
|
+
}), { x: 0, y: 0 });
|
|
319
|
+
const targetRectBeforeAncestorScroll = shiftRect(params.snapshot.locatorRect, -accumulatedDeltaBeforeAncestor.x, -accumulatedDeltaBeforeAncestor.y);
|
|
320
|
+
const targetRectInAncestor = {
|
|
321
|
+
x: targetRectBeforeAncestorScroll.x - ancestor.rect.left,
|
|
322
|
+
y: targetRectBeforeAncestorScroll.y - ancestor.rect.top,
|
|
323
|
+
width: targetRectBeforeAncestorScroll.width,
|
|
324
|
+
height: targetRectBeforeAncestorScroll.height,
|
|
325
|
+
};
|
|
326
|
+
const targetLeftRange = resolveVisibleScrollTargetRangeForAxis({
|
|
327
|
+
scrollStart: ancestor.scrollLeft,
|
|
328
|
+
rectStart: targetRectInAncestor.x,
|
|
329
|
+
rectSize: targetRectInAncestor.width,
|
|
330
|
+
containerSize: ancestor.clientWidth,
|
|
331
|
+
scrollSize: ancestor.scrollWidth,
|
|
332
|
+
});
|
|
333
|
+
const targetTopRange = resolveVisibleScrollTargetRangeForAxis({
|
|
334
|
+
scrollStart: ancestor.scrollTop,
|
|
335
|
+
rectStart: targetRectInAncestor.y,
|
|
336
|
+
rectSize: targetRectInAncestor.height,
|
|
337
|
+
containerSize: ancestor.clientHeight,
|
|
338
|
+
scrollSize: ancestor.scrollHeight,
|
|
339
|
+
});
|
|
340
|
+
const plan = nextPlans[index];
|
|
341
|
+
const nextTargetLeft = clamp(plan.targetLeft + remainingScrollLeft, targetLeftRange.min, targetLeftRange.max);
|
|
342
|
+
const nextTargetTop = clamp(plan.targetTop + remainingScrollTop, targetTopRange.min, targetTopRange.max);
|
|
343
|
+
const appliedScrollLeft = nextTargetLeft - plan.targetLeft;
|
|
344
|
+
const appliedScrollTop = nextTargetTop - plan.targetTop;
|
|
345
|
+
if (!positionsDiffer(plan.targetLeft, nextTargetLeft) &&
|
|
346
|
+
!positionsDiffer(plan.targetTop, nextTargetTop)) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
plan.targetLeft = nextTargetLeft;
|
|
350
|
+
plan.targetTop = nextTargetTop;
|
|
351
|
+
remainingScrollLeft -= appliedScrollLeft;
|
|
352
|
+
remainingScrollTop -= appliedScrollTop;
|
|
353
|
+
changed = true;
|
|
354
|
+
}
|
|
355
|
+
if (!changed) {
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return nextPlans;
|
|
360
|
+
}
|
|
361
|
+
export function buildAncestorScrollPlans(params) {
|
|
362
|
+
const { snapshot, projectedRectRangeX, projectedRectRangeY } = params;
|
|
363
|
+
const plans = [];
|
|
364
|
+
let accumulatedDelta = { x: 0, y: 0 };
|
|
365
|
+
for (const ancestor of snapshot.ancestors) {
|
|
366
|
+
const targetRectBeforeAncestorScroll = shiftRect(snapshot.locatorRect, -accumulatedDelta.x, -accumulatedDelta.y);
|
|
367
|
+
const targetRectInAncestor = {
|
|
368
|
+
x: targetRectBeforeAncestorScroll.x - ancestor.rect.left,
|
|
369
|
+
y: targetRectBeforeAncestorScroll.y - ancestor.rect.top,
|
|
370
|
+
width: targetRectBeforeAncestorScroll.width,
|
|
371
|
+
height: targetRectBeforeAncestorScroll.height,
|
|
372
|
+
};
|
|
373
|
+
const targetTop = resolveMinimalVisibleScrollTargetForAxis({
|
|
374
|
+
scrollStart: ancestor.scrollTop,
|
|
375
|
+
rectStart: targetRectInAncestor.y,
|
|
376
|
+
rectSize: targetRectInAncestor.height,
|
|
377
|
+
containerSize: ancestor.clientHeight,
|
|
378
|
+
scrollSize: ancestor.scrollHeight,
|
|
379
|
+
});
|
|
380
|
+
const targetLeft = resolveMinimalVisibleScrollTargetForAxis({
|
|
381
|
+
scrollStart: ancestor.scrollLeft,
|
|
382
|
+
rectStart: targetRectInAncestor.x,
|
|
383
|
+
rectSize: targetRectInAncestor.width,
|
|
384
|
+
containerSize: ancestor.clientWidth,
|
|
385
|
+
scrollSize: ancestor.scrollWidth,
|
|
386
|
+
});
|
|
387
|
+
plans.push({
|
|
388
|
+
startTop: ancestor.scrollTop,
|
|
389
|
+
startLeft: ancestor.scrollLeft,
|
|
390
|
+
targetTop,
|
|
391
|
+
targetLeft,
|
|
392
|
+
});
|
|
393
|
+
accumulatedDelta = {
|
|
394
|
+
x: accumulatedDelta.x + (targetLeft - ancestor.scrollLeft),
|
|
395
|
+
y: accumulatedDelta.y + (targetTop - ancestor.scrollTop),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const refinedPlans = refineAncestorPlansForProjectedRectRange({
|
|
399
|
+
snapshot,
|
|
400
|
+
plans,
|
|
401
|
+
projectedRectRangeX,
|
|
402
|
+
projectedRectRangeY,
|
|
403
|
+
});
|
|
404
|
+
const projectedRect = projectRectFromAncestorPlans(snapshot, refinedPlans);
|
|
405
|
+
const refinedAccumulatedDelta = {
|
|
406
|
+
x: snapshot.locatorRect.x - projectedRect.x,
|
|
407
|
+
y: snapshot.locatorRect.y - projectedRect.y,
|
|
408
|
+
};
|
|
409
|
+
return {
|
|
410
|
+
plans: refinedPlans,
|
|
411
|
+
accumulatedDelta: refinedAccumulatedDelta,
|
|
412
|
+
projectedRect,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
export function buildPageScrollPlan(snapshot, ancestorProjection, options) {
|
|
416
|
+
const targetY = options.residualOnly !== undefined
|
|
417
|
+
? clamp(snapshot.page.scrollY + options.residualOnly.y, 0, Math.max(0, snapshot.page.scrollHeight - snapshot.viewportSize.height))
|
|
418
|
+
: clamp(snapshot.page.scrollY +
|
|
419
|
+
(ancestorProjection.projectedRect.y -
|
|
420
|
+
options.targetRectPositionInViewport.y), 0, Math.max(0, snapshot.page.scrollHeight - snapshot.viewportSize.height));
|
|
421
|
+
const targetX = options.residualOnly !== undefined
|
|
422
|
+
? clamp(snapshot.page.scrollX + options.residualOnly.x, 0, Math.max(0, snapshot.page.scrollWidth - snapshot.viewportSize.width))
|
|
423
|
+
: clamp(snapshot.page.scrollX +
|
|
424
|
+
(ancestorProjection.projectedRect.x -
|
|
425
|
+
options.targetRectPositionInViewport.x), 0, Math.max(0, snapshot.page.scrollWidth - snapshot.viewportSize.width));
|
|
426
|
+
const finalLocatorRect = shiftRect(ancestorProjection.projectedRect, -(targetX - snapshot.page.scrollX), -(targetY - snapshot.page.scrollY));
|
|
427
|
+
return {
|
|
428
|
+
plan: {
|
|
429
|
+
startY: snapshot.page.scrollY,
|
|
430
|
+
startX: snapshot.page.scrollX,
|
|
431
|
+
targetY,
|
|
432
|
+
targetX,
|
|
433
|
+
},
|
|
434
|
+
finalLocatorRect,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function resolvePagePlan(params) {
|
|
438
|
+
const zoomTargetWithoutPageScroll = params.currentZoomEnd !== undefined
|
|
439
|
+
? resolveZoomTarget({
|
|
440
|
+
locatorRect: params.ancestorResult.projectedRect,
|
|
441
|
+
viewport: params.snapshot.viewportSize,
|
|
442
|
+
targetViewport: params.targetViewport,
|
|
443
|
+
targetRectPositionInZoomViewport: params.targetRectPositionInZoomViewport,
|
|
444
|
+
currentZoomEnd: params.currentZoomEnd,
|
|
445
|
+
})
|
|
446
|
+
: resolveZoomTarget({
|
|
447
|
+
locatorRect: params.ancestorResult.projectedRect,
|
|
448
|
+
viewport: params.snapshot.viewportSize,
|
|
449
|
+
targetViewport: params.targetViewport,
|
|
450
|
+
targetRectPositionInZoomViewport: params.targetRectPositionInZoomViewport,
|
|
451
|
+
});
|
|
452
|
+
const zoomOptimalOffsetWithoutPageScroll = zoomTargetWithoutPageScroll?.optimalOffset;
|
|
453
|
+
const pageScrollCanBeSkipped = zoomOptimalOffsetWithoutPageScroll !== undefined &&
|
|
454
|
+
Math.round(zoomOptimalOffsetWithoutPageScroll.x) === 0 &&
|
|
455
|
+
Math.round(zoomOptimalOffsetWithoutPageScroll.y) === 0;
|
|
456
|
+
const pageResult = buildPageScrollPlan(params.snapshot, params.ancestorResult, {
|
|
457
|
+
targetRectPositionInViewport: params.targetRectPositionInViewport,
|
|
458
|
+
...(zoomOptimalOffsetWithoutPageScroll !== undefined
|
|
459
|
+
? {
|
|
460
|
+
residualOnly: {
|
|
461
|
+
x: zoomOptimalOffsetWithoutPageScroll.x,
|
|
462
|
+
y: zoomOptimalOffsetWithoutPageScroll.y,
|
|
463
|
+
},
|
|
464
|
+
}
|
|
465
|
+
: {}),
|
|
466
|
+
});
|
|
467
|
+
const resolvedPageResult = pageScrollCanBeSkipped
|
|
468
|
+
? {
|
|
469
|
+
plan: {
|
|
470
|
+
startY: params.snapshot.page.scrollY,
|
|
471
|
+
startX: params.snapshot.page.scrollX,
|
|
472
|
+
targetY: params.snapshot.page.scrollY,
|
|
473
|
+
targetX: params.snapshot.page.scrollX,
|
|
474
|
+
},
|
|
475
|
+
finalLocatorRect: params.ancestorResult.projectedRect,
|
|
476
|
+
}
|
|
477
|
+
: pageResult;
|
|
478
|
+
return {
|
|
479
|
+
...resolvedPageResult,
|
|
480
|
+
scrollNeeded: params.ancestorResult.plans.some((plan) => positionsDiffer(plan.startTop, plan.targetTop) ||
|
|
481
|
+
positionsDiffer(plan.startLeft, plan.targetLeft)) ||
|
|
482
|
+
positionsDiffer(resolvedPageResult.plan.startY, resolvedPageResult.plan.targetY) ||
|
|
483
|
+
positionsDiffer(resolvedPageResult.plan.startX, resolvedPageResult.plan.targetX),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
export function combineFocusPlan(params) {
|
|
487
|
+
const targetViewport = resolveFixedFocusViewportSize(params.snapshot.viewportSize, params.amount);
|
|
488
|
+
// Resolve the desired zoom viewport and where the visible locator rect should sit inside it.
|
|
489
|
+
const initialTargetRectPositionInViewport = resolveTargetRectPosition({
|
|
490
|
+
containerSize: params.snapshot.viewportSize,
|
|
491
|
+
rect: params.snapshot.locatorRect,
|
|
492
|
+
amount: params.amount,
|
|
493
|
+
centering: params.centering,
|
|
494
|
+
});
|
|
495
|
+
// Reveal the locator through nested scroll containers using minimal scrolling,
|
|
496
|
+
// plus only the extra needed when page scroll and zoom would otherwise be unable to frame it.
|
|
497
|
+
const ancestorResult = buildAncestorScrollPlans({
|
|
498
|
+
snapshot: params.snapshot,
|
|
499
|
+
projectedRectRangeX: resolveProjectedRectAcceptableRangeForAxis({
|
|
500
|
+
pageScrollStart: params.snapshot.page.scrollX,
|
|
501
|
+
pageScrollMax: Math.max(0, params.snapshot.page.scrollWidth - params.snapshot.viewportSize.width),
|
|
502
|
+
viewportSize: params.snapshot.viewportSize.width,
|
|
503
|
+
targetViewportSize: targetViewport.width,
|
|
504
|
+
targetRectStartInViewport: initialTargetRectPositionInViewport.x,
|
|
505
|
+
}),
|
|
506
|
+
projectedRectRangeY: resolveProjectedRectAcceptableRangeForAxis({
|
|
507
|
+
pageScrollStart: params.snapshot.page.scrollY,
|
|
508
|
+
pageScrollMax: Math.max(0, params.snapshot.page.scrollHeight - params.snapshot.viewportSize.height),
|
|
509
|
+
viewportSize: params.snapshot.viewportSize.height,
|
|
510
|
+
targetViewportSize: targetViewport.height,
|
|
511
|
+
targetRectStartInViewport: initialTargetRectPositionInViewport.y,
|
|
512
|
+
}),
|
|
513
|
+
});
|
|
514
|
+
const targetRectPositionInViewport = resolveTargetRectPosition({
|
|
515
|
+
containerSize: params.snapshot.viewportSize,
|
|
516
|
+
rect: ancestorResult.projectedRect,
|
|
517
|
+
amount: params.amount,
|
|
518
|
+
centering: params.centering,
|
|
519
|
+
});
|
|
520
|
+
// Resolve where the locator rect should sit inside the zoom viewport itself.
|
|
521
|
+
const targetRectPositionInZoomViewport = resolveTargetRectPosition({
|
|
522
|
+
containerSize: targetViewport,
|
|
523
|
+
rect: ancestorResult.projectedRect,
|
|
524
|
+
amount: 1,
|
|
525
|
+
centering: params.centering,
|
|
526
|
+
});
|
|
527
|
+
// Use page scroll only for the framing residual that zoom cannot absorb.
|
|
528
|
+
const resolvedPageResult = resolvePagePlan({
|
|
529
|
+
snapshot: params.snapshot,
|
|
530
|
+
ancestorResult,
|
|
531
|
+
targetRectPositionInViewport,
|
|
532
|
+
targetViewport,
|
|
533
|
+
targetRectPositionInZoomViewport,
|
|
534
|
+
currentZoomEnd: params.currentZoomEnd,
|
|
535
|
+
});
|
|
536
|
+
// Recompute the final zoom target after any page scroll has been applied.
|
|
537
|
+
const zoomTarget = params.currentZoomEnd !== undefined
|
|
538
|
+
? resolveZoomTarget({
|
|
539
|
+
locatorRect: resolvedPageResult.finalLocatorRect,
|
|
540
|
+
viewport: params.snapshot.viewportSize,
|
|
541
|
+
targetViewport,
|
|
542
|
+
targetRectPositionInZoomViewport,
|
|
543
|
+
currentZoomEnd: params.currentZoomEnd,
|
|
544
|
+
})
|
|
545
|
+
: resolveZoomTarget({
|
|
546
|
+
locatorRect: resolvedPageResult.finalLocatorRect,
|
|
547
|
+
viewport: params.snapshot.viewportSize,
|
|
548
|
+
targetViewport,
|
|
549
|
+
targetRectPositionInZoomViewport,
|
|
550
|
+
});
|
|
551
|
+
const zoomNeeded = zoomTarget !== undefined &&
|
|
552
|
+
(params.currentZoomEnd === undefined ||
|
|
553
|
+
params.currentZoomEnd.pointPx.x !== zoomTarget.end.pointPx.x ||
|
|
554
|
+
params.currentZoomEnd.pointPx.y !== zoomTarget.end.pointPx.y ||
|
|
555
|
+
params.currentZoomEnd.size.widthPx !== zoomTarget.end.size.widthPx ||
|
|
556
|
+
params.currentZoomEnd.size.heightPx !== zoomTarget.end.size.heightPx);
|
|
557
|
+
return {
|
|
558
|
+
finalLocatorRect: resolvedPageResult.finalLocatorRect,
|
|
559
|
+
ancestorScrollPlans: ancestorResult.plans,
|
|
560
|
+
pageScrollPlan: resolvedPageResult.plan,
|
|
561
|
+
scrollNeeded: resolvedPageResult.scrollNeeded,
|
|
562
|
+
zoomNeeded,
|
|
563
|
+
finalFocusPoint: {
|
|
564
|
+
x: resolvedPageResult.finalLocatorRect.x +
|
|
565
|
+
resolvedPageResult.finalLocatorRect.width / 2,
|
|
566
|
+
y: resolvedPageResult.finalLocatorRect.y +
|
|
567
|
+
resolvedPageResult.finalLocatorRect.height / 2,
|
|
568
|
+
},
|
|
569
|
+
optimalOffset: zoomTarget?.optimalOffset ?? { x: 0, y: 0 },
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function executeScrollAndZoomPlan(params) {
|
|
573
|
+
const { locator, ancestorScrollPlans, pageScrollPlan, zoomNeeded, duration, easing, } = params;
|
|
574
|
+
const scrolled = ancestorScrollPlans.some((plan) => positionsDiffer(plan.startTop, plan.targetTop) ||
|
|
575
|
+
positionsDiffer(plan.startLeft, plan.targetLeft)) ||
|
|
576
|
+
positionsDiffer(pageScrollPlan.startY, pageScrollPlan.targetY) ||
|
|
577
|
+
positionsDiffer(pageScrollPlan.startX, pageScrollPlan.targetX);
|
|
578
|
+
const zoomed = zoomNeeded;
|
|
579
|
+
if (!scrolled && !zoomed)
|
|
580
|
+
return undefined;
|
|
581
|
+
const startMs = Date.now();
|
|
582
|
+
if (scrolled) {
|
|
583
|
+
await locator.evaluate((element, args) => new Promise((resolve) => {
|
|
584
|
+
const frameMs = 1000 / 60;
|
|
585
|
+
const evaluateEasingAtT = Function(`return (${args.evaluateEasingAtTSource})`)();
|
|
586
|
+
const doc = element.ownerDocument;
|
|
587
|
+
const win = doc.defaultView;
|
|
588
|
+
if (!win) {
|
|
589
|
+
resolve();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const isScrollable = (node) => {
|
|
593
|
+
if (!node ||
|
|
594
|
+
typeof node !== 'object' ||
|
|
595
|
+
!('getBoundingClientRect' in node) ||
|
|
596
|
+
!('clientHeight' in node) ||
|
|
597
|
+
!('clientWidth' in node) ||
|
|
598
|
+
!('scrollHeight' in node) ||
|
|
599
|
+
!('scrollWidth' in node) ||
|
|
600
|
+
!('scrollTop' in node) ||
|
|
601
|
+
!('scrollLeft' in node)) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
const el = node;
|
|
605
|
+
const style = win.getComputedStyle(node);
|
|
606
|
+
return (((style.overflowY === 'auto' ||
|
|
607
|
+
style.overflowY === 'scroll' ||
|
|
608
|
+
style.overflowY === 'overlay') &&
|
|
609
|
+
el.scrollHeight > el.clientHeight) ||
|
|
610
|
+
((style.overflowX === 'auto' ||
|
|
611
|
+
style.overflowX === 'scroll' ||
|
|
612
|
+
style.overflowX === 'overlay') &&
|
|
613
|
+
el.scrollWidth > el.clientWidth));
|
|
614
|
+
};
|
|
615
|
+
const ancestors = [];
|
|
616
|
+
for (let current = element.parentElement; current; current = current.parentElement) {
|
|
617
|
+
if (!isScrollable(current) ||
|
|
618
|
+
current === doc.documentElement ||
|
|
619
|
+
current === doc.body) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
ancestors.push(current);
|
|
623
|
+
}
|
|
624
|
+
const steps = Math.max(1, Math.floor(args.duration / frameMs));
|
|
625
|
+
let step = 0;
|
|
626
|
+
const scheduleNextFrame = () => {
|
|
627
|
+
if (typeof win.requestAnimationFrame === 'function') {
|
|
628
|
+
win.requestAnimationFrame(() => tick());
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
setTimeout(tick, frameMs);
|
|
632
|
+
};
|
|
633
|
+
const tick = () => {
|
|
634
|
+
step += 1;
|
|
635
|
+
const easedT = evaluateEasingAtT(step / steps, args.easing);
|
|
636
|
+
for (const [index, plan] of args.ancestorScrollPlans.entries()) {
|
|
637
|
+
const ancestor = ancestors[index];
|
|
638
|
+
if (!ancestor)
|
|
639
|
+
continue;
|
|
640
|
+
if (!positionsDiffer(plan.startTop, plan.targetTop) &&
|
|
641
|
+
!positionsDiffer(plan.startLeft, plan.targetLeft)) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
ancestor.scrollTop =
|
|
645
|
+
plan.startTop + (plan.targetTop - plan.startTop) * easedT;
|
|
646
|
+
ancestor.scrollLeft =
|
|
647
|
+
plan.startLeft + (plan.targetLeft - plan.startLeft) * easedT;
|
|
648
|
+
}
|
|
649
|
+
win.scrollTo({
|
|
650
|
+
top: args.pageScrollPlan.startY +
|
|
651
|
+
(args.pageScrollPlan.targetY - args.pageScrollPlan.startY) *
|
|
652
|
+
easedT,
|
|
653
|
+
left: args.pageScrollPlan.startX +
|
|
654
|
+
(args.pageScrollPlan.targetX - args.pageScrollPlan.startX) *
|
|
655
|
+
easedT,
|
|
656
|
+
behavior: 'auto',
|
|
657
|
+
});
|
|
658
|
+
if (step >= steps) {
|
|
659
|
+
resolve();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
scheduleNextFrame();
|
|
663
|
+
};
|
|
664
|
+
tick();
|
|
665
|
+
}), {
|
|
666
|
+
ancestorScrollPlans,
|
|
667
|
+
pageScrollPlan,
|
|
668
|
+
duration,
|
|
669
|
+
easing,
|
|
670
|
+
evaluateEasingAtTSource: evaluateEasingAtT.toString(),
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
else if (zoomed && duration > 0) {
|
|
674
|
+
await sleep(duration);
|
|
675
|
+
}
|
|
676
|
+
const endMs = Date.now();
|
|
677
|
+
return {
|
|
678
|
+
...(scrolled
|
|
679
|
+
? {
|
|
680
|
+
scroll: {
|
|
681
|
+
startMs,
|
|
682
|
+
endMs,
|
|
683
|
+
...(duration > 0 ? { easing } : {}),
|
|
684
|
+
},
|
|
685
|
+
}
|
|
686
|
+
: {}),
|
|
687
|
+
...(zoomed
|
|
688
|
+
? {
|
|
689
|
+
zoom: {
|
|
690
|
+
startMs,
|
|
691
|
+
endMs,
|
|
692
|
+
...(duration > 0 ? { easing } : {}),
|
|
693
|
+
},
|
|
694
|
+
}
|
|
695
|
+
: {}),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function resolveMouseMovePlan(params) {
|
|
699
|
+
const { mouseMove, mouseTarget } = params;
|
|
700
|
+
if (mouseMove === undefined)
|
|
701
|
+
return undefined;
|
|
702
|
+
return {
|
|
703
|
+
mouseTarget,
|
|
704
|
+
scrollAndZoomTiming: resolveScrollAndZoomTimingPlan({
|
|
705
|
+
viewportSize: params.viewportSize,
|
|
706
|
+
target: mouseTarget,
|
|
707
|
+
startViewportPos: params.startViewportPos,
|
|
708
|
+
duration: params.duration,
|
|
709
|
+
easing: params.easing,
|
|
710
|
+
cursorTriggerEdgeThreshold: params.cursorTriggerEdgeThreshold,
|
|
711
|
+
cursorTriggerMaxProgress: params.cursorTriggerMaxProgress,
|
|
712
|
+
}),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function resolveFocusOptions(params) {
|
|
716
|
+
const resolvedAutoZoomOptions = resolveAutoZoomOptions(params.state, params.options);
|
|
717
|
+
const focusOptions = params.state.insideAutoZoom
|
|
718
|
+
? resolvedAutoZoomOptions
|
|
719
|
+
: {
|
|
720
|
+
...resolvedAutoZoomOptions,
|
|
721
|
+
amount: 1,
|
|
722
|
+
centering: DEFAULT_ZOOM_OPTIONS.centering,
|
|
723
|
+
};
|
|
724
|
+
const currentZoomViewport = params.state.currentZoomViewport;
|
|
725
|
+
const currentZoomEnd = currentZoomViewport?.end ?? {
|
|
726
|
+
pointPx: { x: 0, y: 0 },
|
|
727
|
+
size: {
|
|
728
|
+
widthPx: params.viewportSize.width,
|
|
729
|
+
heightPx: params.viewportSize.height,
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
if (currentZoomViewport !== undefined &&
|
|
733
|
+
currentZoomEnd.pointPx.x === 0 &&
|
|
734
|
+
currentZoomEnd.pointPx.y === 0 &&
|
|
735
|
+
currentZoomEnd.size.widthPx === params.viewportSize.width &&
|
|
736
|
+
currentZoomEnd.size.heightPx === params.viewportSize.height) {
|
|
737
|
+
focusOptions.centering = 1;
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
focusOptions,
|
|
741
|
+
currentZoomEnd,
|
|
742
|
+
timing: {
|
|
743
|
+
duration: focusOptions.duration,
|
|
744
|
+
easing: focusOptions.easing,
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
export async function changeFocus(locator, options = {}, mouseMove) {
|
|
749
|
+
const page = locator.page();
|
|
750
|
+
const state = getAutoZoomState();
|
|
751
|
+
const snapshot = await captureFocusSnapshot(locator);
|
|
752
|
+
const startViewportPos = getMousePosition(page) ?? {
|
|
753
|
+
x: snapshot.viewportSize.width / 2,
|
|
754
|
+
y: snapshot.viewportSize.height / 2,
|
|
755
|
+
};
|
|
756
|
+
const { focusOptions, currentZoomEnd, timing } = resolveFocusOptions(mouseMove !== undefined
|
|
757
|
+
? {
|
|
758
|
+
state,
|
|
759
|
+
options,
|
|
760
|
+
viewportSize: snapshot.viewportSize,
|
|
761
|
+
mouseMove,
|
|
762
|
+
}
|
|
763
|
+
: {
|
|
764
|
+
state,
|
|
765
|
+
options,
|
|
766
|
+
viewportSize: snapshot.viewportSize,
|
|
767
|
+
});
|
|
768
|
+
const plan = combineFocusPlan({
|
|
769
|
+
snapshot,
|
|
770
|
+
amount: focusOptions.amount,
|
|
771
|
+
centering: focusOptions.centering,
|
|
772
|
+
currentZoomEnd,
|
|
773
|
+
});
|
|
774
|
+
const mouseTarget = mouseMove?.targetPosInElement
|
|
775
|
+
? {
|
|
776
|
+
x: plan.finalLocatorRect.x + mouseMove.targetPosInElement.x,
|
|
777
|
+
y: plan.finalLocatorRect.y + mouseMove.targetPosInElement.y,
|
|
778
|
+
}
|
|
779
|
+
: {
|
|
780
|
+
x: plan.finalLocatorRect.x + plan.finalLocatorRect.width / 2,
|
|
781
|
+
y: plan.finalLocatorRect.y + plan.finalLocatorRect.height / 2,
|
|
782
|
+
};
|
|
783
|
+
const mouseMovePlan = resolveMouseMovePlan({
|
|
784
|
+
mouseMove,
|
|
785
|
+
startViewportPos,
|
|
786
|
+
viewportSize: snapshot.viewportSize,
|
|
787
|
+
duration: timing.duration,
|
|
788
|
+
easing: timing.easing,
|
|
789
|
+
cursorTriggerEdgeThreshold: CURSOR_TRIGGER_EDGE_THRESHOLD,
|
|
790
|
+
cursorTriggerMaxProgress: CURSOR_TRIGGER_MAX_PROGRESS,
|
|
791
|
+
mouseTarget,
|
|
792
|
+
});
|
|
793
|
+
const focusChangeStartMs = Date.now();
|
|
794
|
+
if (focusOptions.preZoomDelay > 0) {
|
|
795
|
+
await sleep(focusOptions.preZoomDelay);
|
|
796
|
+
}
|
|
797
|
+
const mousePromise = mouseMovePlan !== undefined
|
|
798
|
+
? performMouseMove({
|
|
799
|
+
page,
|
|
800
|
+
targetX: mouseMovePlan.mouseTarget.x,
|
|
801
|
+
targetY: mouseMovePlan.mouseTarget.y,
|
|
802
|
+
duration: timing.duration,
|
|
803
|
+
easing: timing.easing,
|
|
804
|
+
})
|
|
805
|
+
: Promise.resolve(undefined);
|
|
806
|
+
const scrollAndZoomPromise = mouseMovePlan?.scrollAndZoomTiming.startDelay !== undefined &&
|
|
807
|
+
mouseMovePlan.scrollAndZoomTiming.startDelay > 0
|
|
808
|
+
? sleep(mouseMovePlan.scrollAndZoomTiming.startDelay).then(() => executeScrollAndZoomPlan({
|
|
809
|
+
locator,
|
|
810
|
+
ancestorScrollPlans: plan.ancestorScrollPlans,
|
|
811
|
+
pageScrollPlan: plan.pageScrollPlan,
|
|
812
|
+
zoomNeeded: plan.zoomNeeded,
|
|
813
|
+
duration: mouseMovePlan.scrollAndZoomTiming.duration,
|
|
814
|
+
easing: timing.easing,
|
|
815
|
+
}))
|
|
816
|
+
: executeScrollAndZoomPlan({
|
|
817
|
+
locator,
|
|
818
|
+
ancestorScrollPlans: plan.ancestorScrollPlans,
|
|
819
|
+
pageScrollPlan: plan.pageScrollPlan,
|
|
820
|
+
zoomNeeded: plan.zoomNeeded,
|
|
821
|
+
duration: mouseMovePlan?.scrollAndZoomTiming.duration ?? timing.duration,
|
|
822
|
+
easing: timing.easing,
|
|
823
|
+
});
|
|
824
|
+
const [mouseMoveResult, scrollAndZoomResult] = await Promise.all([
|
|
825
|
+
mousePromise,
|
|
826
|
+
scrollAndZoomPromise,
|
|
827
|
+
]);
|
|
828
|
+
const zoomTarget = resolveZoomTarget({
|
|
829
|
+
locatorRect: plan.finalLocatorRect,
|
|
830
|
+
viewport: snapshot.viewportSize,
|
|
831
|
+
targetViewport: resolveFixedFocusViewportSize(snapshot.viewportSize, focusOptions.amount),
|
|
832
|
+
targetRectPositionInZoomViewport: resolveTargetRectPosition({
|
|
833
|
+
containerSize: resolveFixedFocusViewportSize(snapshot.viewportSize, focusOptions.amount),
|
|
834
|
+
rect: plan.finalLocatorRect,
|
|
835
|
+
amount: 1,
|
|
836
|
+
centering: focusOptions.centering,
|
|
837
|
+
}),
|
|
838
|
+
...(currentZoomEnd !== undefined ? { currentZoomEnd } : {}),
|
|
839
|
+
});
|
|
840
|
+
const zoomEvent = buildZoomEvent({
|
|
841
|
+
target: zoomTarget,
|
|
842
|
+
currentZoomEnd,
|
|
843
|
+
zoomTiming: scrollAndZoomResult?.zoom,
|
|
844
|
+
});
|
|
845
|
+
const focusPoint = mouseMovePlan?.mouseTarget ?? plan.finalFocusPoint;
|
|
846
|
+
const mouseChange = mouseMovePlan !== undefined && mouseMoveResult !== undefined
|
|
847
|
+
? {
|
|
848
|
+
startMs: mouseMoveResult.startMs,
|
|
849
|
+
endMs: mouseMoveResult.endMs,
|
|
850
|
+
...(timing.duration > 0 ? { easing: timing.easing } : {}),
|
|
851
|
+
}
|
|
852
|
+
: undefined;
|
|
853
|
+
if (focusOptions.postZoomDelay > 0) {
|
|
854
|
+
await sleep(focusOptions.postZoomDelay);
|
|
855
|
+
}
|
|
856
|
+
const focusChangeEndMs = Date.now();
|
|
857
|
+
const focusChange = {
|
|
858
|
+
type: 'focusChange',
|
|
859
|
+
startMs: focusChangeStartMs,
|
|
860
|
+
endMs: focusChangeEndMs,
|
|
861
|
+
x: focusPoint.x,
|
|
862
|
+
y: focusPoint.y,
|
|
863
|
+
...(mouseChange !== undefined ? { mouse: mouseChange } : {}),
|
|
864
|
+
...(scrollAndZoomResult?.scroll !== undefined
|
|
865
|
+
? { scroll: scrollAndZoomResult.scroll }
|
|
866
|
+
: {}),
|
|
867
|
+
...(zoomEvent !== undefined ? { zoom: zoomEvent } : {}),
|
|
868
|
+
elementRect: plan.finalLocatorRect,
|
|
869
|
+
};
|
|
870
|
+
setCurrentZoomViewport({
|
|
871
|
+
focusPoint,
|
|
872
|
+
elementRect: plan.finalLocatorRect,
|
|
873
|
+
end: zoomTarget?.end ?? {
|
|
874
|
+
pointPx: { x: 0, y: 0 },
|
|
875
|
+
size: {
|
|
876
|
+
widthPx: snapshot.viewportSize.width,
|
|
877
|
+
heightPx: snapshot.viewportSize.height,
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
viewportSize: snapshot.viewportSize,
|
|
881
|
+
optimalOffset: zoomTarget?.optimalOffset ?? { x: 0, y: 0 },
|
|
882
|
+
});
|
|
883
|
+
return focusChange;
|
|
884
|
+
}
|
|
885
|
+
//# sourceMappingURL=changeFocus.js.map
|