@reticlehq/browser 3.5.0 → 3.7.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 +54 -0
- package/dist/actions/action-report.js +60 -0
- package/dist/actions/actions-dom.d.ts +1 -0
- package/dist/actions/actions-dom.js +56 -43
- package/dist/actions/actions-press.d.ts +11 -4
- package/dist/actions/actions-press.js +132 -17
- package/dist/actions/actions.d.ts +1 -35
- package/dist/actions/actions.js +32 -65
- package/dist/actions/appeared-text.js +3 -0
- package/dist/actions/danger-context.d.ts +2 -0
- package/dist/actions/danger-context.js +29 -3
- package/dist/actions/value-input.d.ts +1 -0
- package/dist/actions/value-input.js +11 -0
- package/dist/commands/commands.js +4 -0
- package/dist/connect-options.d.ts +1 -0
- package/dist/dom/a11y.js +264 -14
- package/dist/dom/addressing/coverage-markers.d.ts +6 -0
- package/dist/dom/addressing/coverage-markers.js +13 -0
- package/dist/dom/addressing/refs.js +2 -1
- package/dist/dom/addressing/testid-attr.d.ts +4 -0
- package/dist/dom/addressing/testid-attr.js +14 -0
- package/dist/dom/dom-ignore.d.ts +2 -0
- package/dist/dom/dom-ignore.js +15 -2
- package/dist/dom/query.d.ts +2 -1
- package/dist/dom/query.js +116 -27
- package/dist/dom/render-page.d.ts +12 -0
- package/dist/dom/render-page.js +135 -0
- package/dist/dom/snapshot.js +56 -7
- package/dist/observers/departure.js +51 -9
- package/dist/observers/dom.js +35 -18
- package/dist/observers/field.js +5 -21
- package/dist/observers/health.js +63 -2
- package/dist/observers/network-stream.d.ts +1 -1
- package/dist/observers/network-stream.js +3 -3
- package/dist/observers/network.js +57 -8
- package/dist/observers/perf.js +53 -1
- package/dist/observers/route.js +6 -5
- package/dist/observers/storage.js +6 -1
- package/dist/presenter/carousel/carousel.d.ts +5 -2
- package/dist/presenter/carousel/carousel.js +30 -8
- package/dist/presenter/carousel/panel-slides.d.ts +3 -4
- package/dist/presenter/carousel/panel-slides.js +48 -9
- package/dist/presenter/carousel/talk-card.d.ts +1 -1
- package/dist/presenter/chrome/log-kinds.d.ts +5 -0
- package/dist/presenter/chrome/log-kinds.js +1 -0
- package/dist/presenter/chrome/log-memory.d.ts +13 -0
- package/dist/presenter/chrome/log-memory.js +39 -0
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
- package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
- package/dist/presenter/chrome/presenter-log.d.ts +12 -4
- package/dist/presenter/chrome/presenter-log.js +100 -10
- package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
- package/dist/presenter/chrome/presenter-morph.js +34 -0
- package/dist/presenter/chrome/presenter-report-copy.d.ts +28 -5
- package/dist/presenter/chrome/presenter-report-copy.js +40 -5
- package/dist/presenter/hud-telemetry.js +83 -3
- package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
- package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
- package/dist/presenter/icons/presenter-icons.d.ts +2 -0
- package/dist/presenter/icons/presenter-icons.js +2 -0
- package/dist/presenter/presenter-account-styles.js +3 -3
- package/dist/presenter/presenter-account.d.ts +31 -8
- package/dist/presenter/presenter-account.js +105 -15
- package/dist/presenter/presenter-agent-link.d.ts +9 -0
- package/dist/presenter/presenter-agent-link.js +62 -0
- package/dist/presenter/presenter-chat-views.d.ts +43 -0
- package/dist/presenter/presenter-chat-views.js +622 -0
- package/dist/presenter/presenter-config.d.ts +5 -2
- package/dist/presenter/presenter-config.js +5 -3
- package/dist/presenter/presenter-controls.d.ts +3 -2
- package/dist/presenter/presenter-controls.js +163 -61
- package/dist/presenter/presenter-design-tokens.d.ts +1 -0
- package/dist/presenter/presenter-design-tokens.js +34 -0
- package/dist/presenter/presenter-dock-layout.js +1 -1
- package/dist/presenter/presenter-frame.d.ts +12 -0
- package/dist/presenter/presenter-frame.js +187 -0
- package/dist/presenter/presenter-harness-row.d.ts +67 -0
- package/dist/presenter/presenter-harness-row.js +209 -0
- package/dist/presenter/presenter-personas.d.ts +35 -0
- package/dist/presenter/presenter-personas.js +95 -0
- package/dist/presenter/presenter-plan.d.ts +10 -0
- package/dist/presenter/presenter-plan.js +186 -0
- package/dist/presenter/presenter-report-styles.js +46 -21
- package/dist/presenter/presenter-report.d.ts +2 -1
- package/dist/presenter/presenter-report.js +123 -20
- package/dist/presenter/presenter-settings-styles.js +18 -4
- package/dist/presenter/presenter-settings.d.ts +8 -2
- package/dist/presenter/presenter-settings.js +134 -33
- package/dist/presenter/presenter-shell-styles.js +48 -42
- package/dist/presenter/presenter-shell.d.ts +17 -4
- package/dist/presenter/presenter-shell.js +139 -79
- package/dist/presenter/presenter-styles.js +9 -3
- package/dist/presenter/presenter-workspace.js +10 -3
- package/dist/presenter/presenter.d.ts +6 -4
- package/dist/presenter/presenter.js +171 -22
- package/dist/presenter/tour/tour.d.ts +4 -1
- package/dist/presenter/tour/tour.js +6 -4
- package/dist/recorder/recorder.js +2 -2
- package/dist/registry/auto-testids.js +3 -4
- package/dist/registry/declared-platform.js +4 -0
- package/dist/reticle-inject.js +938 -494
- package/dist/reticle.d.ts +10 -1
- package/dist/reticle.js +109 -11
- package/dist/review/annotator-styles.d.ts +1 -1
- package/dist/review/annotator.d.ts +7 -0
- package/dist/review/annotator.js +20 -2
- package/dist/review/mark-anchor.js +2 -2
- package/dist/session-continuity.js +2 -1
- package/dist/storage-keys.d.ts +15 -0
- package/dist/storage-keys.js +21 -0
- package/dist/transport/transport.js +5 -3
- package/dist/transport/unreachable-message.d.ts +1 -1
- package/dist/transport/unreachable-message.js +3 -5
- package/package.json +3 -2
- package/dist/presenter/carousel/offer-card.d.ts +0 -21
- package/dist/presenter/carousel/offer-card.js +0 -55
package/dist/actions/actions.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { ActionType, ActionWarning, DANGEROUS_ACTION_CONFIRM_ARG, ElementState, NATIVE_INPUT_ARG, SettleReason, } from '@reticlehq/core';
|
|
1
|
+
import { ActionType, ActionWarning, clampHoldMs, DANGEROUS_ACTION_CONFIRM_ARG, ElementState, NATIVE_INPUT_ARG, SettleReason, } from '@reticlehq/core';
|
|
2
2
|
import { asSyntheticInput } from './synthetic/synthetic-input.js';
|
|
3
3
|
import { echoRef, refs } from '../dom/addressing/refs.js';
|
|
4
|
-
import { dangerousActionContext, requiresDangerousConfirmation, submitControlFor, } from './danger-context.js';
|
|
5
|
-
import { assertEditable, assertNotRichText, setNativeValue } from './value-input.js';
|
|
6
|
-
import {
|
|
7
|
-
import { elementHasHoverHandlers
|
|
4
|
+
import { dangerousActionContext, requiresDangerousConfirmation, assertOutwardAllowed, submitControlFor, } from './danger-context.js';
|
|
5
|
+
import { assertEditable, assertNotRichText, isAutofillSafeTarget, setNativeValue, } from './value-input.js';
|
|
6
|
+
import { getRole, isVisible, getStates } from '../dom/a11y.js';
|
|
7
|
+
import { elementHasHoverHandlers } from '../registry/stores/adapters.js';
|
|
8
8
|
import { isActionTarget, isForm, isInput, isSelect, isTextArea, } from '../dom/realm.js';
|
|
9
9
|
import { nativeSetTimeout, settle } from '../timers/native/native-timers.js';
|
|
10
10
|
import { AppearedText } from './appeared-text.js';
|
|
11
11
|
import { alreadyAtCheckedState, assertAriaToggleLanded, isAriaToggle } from './check-state.js';
|
|
12
|
-
import { focusedOrDocument, isReflessDocumentPress, pressCode, pressKey, pressModifiers, pressKeys, holdKey, pressCombo, } from './actions-press.js';
|
|
12
|
+
import { focusedOrDocument, isReflessDocumentPress, pressCode, pressKey, pressModifiers, pressKeys, holdKey, pressCombo, dispatchKeypress, closeModalOnEscape, keyboardEvent, } from './actions-press.js';
|
|
13
|
+
import { anchorOf, result } from './action-report.js';
|
|
13
14
|
function asString(value, fallback = '') {
|
|
14
15
|
return 'string' === typeof value ? value : fallback;
|
|
15
16
|
}
|
|
@@ -21,49 +22,6 @@ function requireElement(ref) {
|
|
|
21
22
|
throw new Error(`ref '${echoRef(ref)}' is not an HTML or SVG element`);
|
|
22
23
|
return el;
|
|
23
24
|
}
|
|
24
|
-
function anchorOf(el) {
|
|
25
|
-
const testid = el.getAttribute('data-testid') ?? undefined;
|
|
26
|
-
const info = identifyComponent(el);
|
|
27
|
-
const out = {};
|
|
28
|
-
if (testid !== undefined)
|
|
29
|
-
out.testid = testid;
|
|
30
|
-
const component = info?.componentStack[0];
|
|
31
|
-
if (component !== undefined)
|
|
32
|
-
out.component = component;
|
|
33
|
-
if (info?.source !== undefined)
|
|
34
|
-
out.source = info.source;
|
|
35
|
-
const role = getRole(el);
|
|
36
|
-
const name = getAccessibleName(el);
|
|
37
|
-
if (role.length > 0)
|
|
38
|
-
out.role = role;
|
|
39
|
-
if (name.length > 0)
|
|
40
|
-
out.name = name;
|
|
41
|
-
return out;
|
|
42
|
-
}
|
|
43
|
-
const result = (ref, action, effect, settled, settleReason, anchor, warning) => {
|
|
44
|
-
const base = {
|
|
45
|
-
ok: true,
|
|
46
|
-
ref,
|
|
47
|
-
action,
|
|
48
|
-
dispatched: true,
|
|
49
|
-
settled,
|
|
50
|
-
settleReason,
|
|
51
|
-
effect,
|
|
52
|
-
};
|
|
53
|
-
if (anchor.testid !== undefined)
|
|
54
|
-
base.testid = anchor.testid;
|
|
55
|
-
if (anchor.component !== undefined)
|
|
56
|
-
base.component = anchor.component;
|
|
57
|
-
if (anchor.source !== undefined)
|
|
58
|
-
base.source = anchor.source;
|
|
59
|
-
if (anchor.role !== undefined)
|
|
60
|
-
base.role = anchor.role;
|
|
61
|
-
if (anchor.name !== undefined)
|
|
62
|
-
base.name = anchor.name;
|
|
63
|
-
if (warning !== undefined)
|
|
64
|
-
base.warning = warning;
|
|
65
|
-
return base;
|
|
66
|
-
};
|
|
67
25
|
const FILL_LIKE = new Set([
|
|
68
26
|
ActionType.FILL,
|
|
69
27
|
ActionType.TYPE,
|
|
@@ -109,6 +67,8 @@ function assertActionAllowed(el, action, args) {
|
|
|
109
67
|
(action === ActionType.PRESS && 'Enter' === pressKey(args));
|
|
110
68
|
const dragTarget = action === ActionType.DRAG ? refs.resolve(dragTargetRef(args)) : null;
|
|
111
69
|
const submitter = action === ActionType.PRESS && 'Enter' === pressKey(args) ? submitControlFor(el) : null;
|
|
70
|
+
if (canTrigger)
|
|
71
|
+
assertOutwardAllowed([el, submitter, isActionTarget(dragTarget) ? dragTarget : null]);
|
|
112
72
|
const sourceDangerous = requiresDangerousConfirmation(dangerousActionContext(el), getRole(el)) ||
|
|
113
73
|
(submitter !== null &&
|
|
114
74
|
requiresDangerousConfirmation(dangerousActionContext(submitter), getRole(submitter)));
|
|
@@ -120,7 +80,13 @@ function assertActionAllowed(el, action, args) {
|
|
|
120
80
|
throw new Error(`potentially destructive action blocked; retry with args.${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
121
81
|
}
|
|
122
82
|
}
|
|
123
|
-
|
|
83
|
+
function optionValueFor(options, named) {
|
|
84
|
+
if (options.some((option) => option.value === named))
|
|
85
|
+
return named;
|
|
86
|
+
const byLabel = options.filter((option) => option.label.trim() === named.trim());
|
|
87
|
+
return 1 === byLabel.length ? (byLabel[0]?.value ?? named) : named;
|
|
88
|
+
}
|
|
89
|
+
import { NO_GEOMETRY, fireClickSequence, fireTapSequence, clickGeometry, firePointer, firePointerNonBubbling, dragElement, mouseEventFor, assertNotLeaving, } from './actions-dom.js';
|
|
124
90
|
import { scrollFrom } from './scroll.js';
|
|
125
91
|
function enabledOf(el) {
|
|
126
92
|
return !getStates(el).includes(ElementState.DISABLED);
|
|
@@ -137,19 +103,13 @@ function activeRef(el) {
|
|
|
137
103
|
return null;
|
|
138
104
|
return refs.refFor(active);
|
|
139
105
|
}
|
|
140
|
-
const MAX_HOLD_MS = 30_000;
|
|
141
|
-
function clampHold(raw) {
|
|
142
|
-
if ('number' !== typeof raw || !Number.isFinite(raw) || raw <= 0)
|
|
143
|
-
return 0;
|
|
144
|
-
return Math.min(raw, MAX_HOLD_MS);
|
|
145
|
-
}
|
|
146
106
|
async function dispatchFor(el, action, args) {
|
|
147
107
|
if (ActionType.CLICK === action) {
|
|
148
|
-
const hold =
|
|
108
|
+
const hold = clampHoldMs(args['holdMs']);
|
|
149
109
|
return await fireClickSequence(el, 0 === hold ? undefined : { ms: hold, sleep, now: () => Date.now() });
|
|
150
110
|
}
|
|
151
111
|
if (ActionType.TAP === action) {
|
|
152
|
-
const hold =
|
|
112
|
+
const hold = clampHoldMs(args['holdMs']);
|
|
153
113
|
return await fireTapSequence(el, 0 === hold ? undefined : { ms: hold, sleep, now: () => Date.now() });
|
|
154
114
|
}
|
|
155
115
|
return { prevented: await dispatchOther(el, action, args), heldMs: 0 };
|
|
@@ -175,7 +135,7 @@ async function dispatchOther(el, action, args) {
|
|
|
175
135
|
el.dispatchEvent(mouseEventFor(el, 'mouseenter', { relatedTarget: from }));
|
|
176
136
|
firePointer(el, 'pointermove', from);
|
|
177
137
|
const moved = el.dispatchEvent(mouseEventFor(el, 'mousemove', { bubbles: true, cancelable: true }));
|
|
178
|
-
const holdMs =
|
|
138
|
+
const holdMs = clampHoldMs(args['holdMs']);
|
|
179
139
|
if (holdMs > 0)
|
|
180
140
|
await sleep(holdMs);
|
|
181
141
|
return !moved;
|
|
@@ -201,7 +161,8 @@ async function dispatchOther(el, action, args) {
|
|
|
201
161
|
'To empty a field on purpose use the `clear` action, or fill with an explicit "".');
|
|
202
162
|
}
|
|
203
163
|
assertEditable(el, 'fill');
|
|
204
|
-
el
|
|
164
|
+
if (!isAutofillSafeTarget(el))
|
|
165
|
+
el.focus();
|
|
205
166
|
return setNativeValue(el, args['value']);
|
|
206
167
|
}
|
|
207
168
|
assertNotRichText(el, 'fill');
|
|
@@ -227,8 +188,8 @@ async function dispatchOther(el, action, args) {
|
|
|
227
188
|
if (typeof args['value'] !== 'string') {
|
|
228
189
|
throw new Error("select requires a string `value` — pass it nested, as args: { value: '…' }.");
|
|
229
190
|
}
|
|
230
|
-
const wanted = args['value'];
|
|
231
191
|
const options = Array.from(el.options);
|
|
192
|
+
const wanted = optionValueFor(options, args['value']);
|
|
232
193
|
if (!options.some((option) => option.value === wanted)) {
|
|
233
194
|
const available = options.map((option) => `${option.value} (${option.label})`).join(', ');
|
|
234
195
|
throw new Error(`no <option> with value '${echoRef(wanted)}' — available: ${0 === options.length ? '(none)' : available}`);
|
|
@@ -274,14 +235,17 @@ async function dispatchOther(el, action, args) {
|
|
|
274
235
|
const mods = pressModifiers(args);
|
|
275
236
|
const combo = pressKeys(args);
|
|
276
237
|
if (combo.length > 0)
|
|
277
|
-
return await pressCombo(el, combo, mods,
|
|
238
|
+
return await pressCombo(el, combo, mods, clampHoldMs(args['holdMs']));
|
|
278
239
|
const key = pressKey(args);
|
|
279
240
|
const code = pressCode(args, key);
|
|
280
|
-
const down = asSyntheticInput(() => el.dispatchEvent(
|
|
281
|
-
|
|
241
|
+
const down = asSyntheticInput(() => el.dispatchEvent(keyboardEvent('keydown', { key, code, bubbles: true, cancelable: true, ...mods })));
|
|
242
|
+
closeModalOnEscape(el, key, down);
|
|
243
|
+
if (down)
|
|
244
|
+
dispatchKeypress(el, key, code, mods);
|
|
245
|
+
const hold = clampHoldMs(args['holdMs']);
|
|
282
246
|
if (hold > 0)
|
|
283
247
|
await holdKey(el, key, code, mods, hold);
|
|
284
|
-
asSyntheticInput(() => el.dispatchEvent(
|
|
248
|
+
asSyntheticInput(() => el.dispatchEvent(keyboardEvent('keyup', { key, code, bubbles: true, ...mods })));
|
|
285
249
|
return !down;
|
|
286
250
|
}
|
|
287
251
|
case ActionType.SCROLL_INTO_VIEW:
|
|
@@ -346,6 +310,8 @@ export async function executeAction(ref, action, args = {}) {
|
|
|
346
310
|
const el = isReflessDocumentPress(ref, action, args) ? focusedOrDocument() : requireElement(ref);
|
|
347
311
|
if (named)
|
|
348
312
|
assertActionAllowed(el, action, args);
|
|
313
|
+
if (named)
|
|
314
|
+
assertNotLeaving(el);
|
|
349
315
|
const anchor = named ? anchorOf(el) : {};
|
|
350
316
|
const visible = isVisible(el);
|
|
351
317
|
const enabled = enabledOf(el);
|
|
@@ -392,6 +358,7 @@ export async function executeAction(ref, action, args = {}) {
|
|
|
392
358
|
defaultPrevented,
|
|
393
359
|
focusMoved: prevFocus !== nextFocus ? `${prevFocus ?? 'null'}->${nextFocus ?? 'null'}` : null,
|
|
394
360
|
valueChanged: isFillLike(action) ? valueBefore !== valueAfter : false,
|
|
361
|
+
...(ActionType.FILL === action && isAutofillSafeTarget(el) ? { autofillSafe: true } : {}),
|
|
395
362
|
domMutatedWithin: mutated,
|
|
396
363
|
...said.effect(valueAfter),
|
|
397
364
|
occluded: geometry.occluded,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isIgnored } from '../dom/dom-ignore.js';
|
|
2
|
+
import { isSensitiveField } from '../dom/a11y.js';
|
|
2
3
|
const APPEARED_MAX = 200;
|
|
3
4
|
const JOIN = ' | ';
|
|
4
5
|
const TEXT_NODE = 3;
|
|
@@ -46,6 +47,8 @@ export class AppearedText {
|
|
|
46
47
|
#add(raw, owner) {
|
|
47
48
|
if (null !== owner && isIgnored(owner))
|
|
48
49
|
return;
|
|
50
|
+
if (null !== owner && isSensitiveField(owner))
|
|
51
|
+
return;
|
|
49
52
|
const text = (raw ?? '').replace(/\s+/g, ' ').trim();
|
|
50
53
|
if (0 === text.length)
|
|
51
54
|
return;
|
|
@@ -2,3 +2,5 @@ import { type ActionTarget } from '../dom/realm.js';
|
|
|
2
2
|
export declare function dangerousActionContext(el: ActionTarget): string;
|
|
3
3
|
export declare function submitControlFor(el: ActionTarget): HTMLElement | null;
|
|
4
4
|
export declare function requiresDangerousConfirmation(text: string, role?: string): boolean;
|
|
5
|
+
export declare function setOutwardPolicy(allowed: readonly string[] | undefined): void;
|
|
6
|
+
export declare function assertOutwardAllowed(targets: readonly (ActionTarget | null)[]): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isDangerousActionText } from '@reticlehq/core';
|
|
2
|
-
import { getAccessibleName } from '../dom/a11y.js';
|
|
1
|
+
import { isDangerousActionText, outwardActionOf, outwardRefusal } from '@reticlehq/core';
|
|
2
|
+
import { getAccessibleName, getRole } from '../dom/a11y.js';
|
|
3
3
|
import { isHtmlElement } from '../dom/realm.js';
|
|
4
4
|
const LABEL_VALUED_INPUT_TYPES = new Set([
|
|
5
5
|
'submit',
|
|
@@ -16,7 +16,18 @@ function holdsUserText(el) {
|
|
|
16
16
|
return !LABEL_VALUED_INPUT_TYPES.has(el.type.toLowerCase());
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
+
const ANCHOR_TAG = 'A';
|
|
20
|
+
const INERT_HREF = /^\s*(#.*|javascript:.*)?$/i;
|
|
21
|
+
function navigatingHref(el) {
|
|
22
|
+
if (ANCHOR_TAG !== el.tagName)
|
|
23
|
+
return undefined;
|
|
24
|
+
const href = el.getAttribute('href');
|
|
25
|
+
return null === href || INERT_HREF.test(href) ? undefined : href;
|
|
26
|
+
}
|
|
19
27
|
export function dangerousActionContext(el) {
|
|
28
|
+
const href = navigatingHref(el);
|
|
29
|
+
if (href !== undefined)
|
|
30
|
+
return href;
|
|
20
31
|
const form = el.closest('form');
|
|
21
32
|
const ownText = holdsUserText(el)
|
|
22
33
|
? ['', '']
|
|
@@ -33,8 +44,23 @@ export function dangerousActionContext(el) {
|
|
|
33
44
|
const SUBMIT_CONTROL_SELECTOR = 'button[type="submit"], input[type="submit"], button:not([type]):not([type=""])';
|
|
34
45
|
export function submitControlFor(el) {
|
|
35
46
|
const found = el.closest('form')?.querySelector(SUBMIT_CONTROL_SELECTOR);
|
|
36
|
-
return found
|
|
47
|
+
return isHtmlElement(found) ? found : null;
|
|
37
48
|
}
|
|
38
49
|
export function requiresDangerousConfirmation(text, role) {
|
|
39
50
|
return isDangerousActionText(text, role);
|
|
40
51
|
}
|
|
52
|
+
let outwardAllowed;
|
|
53
|
+
export function setOutwardPolicy(allowed) {
|
|
54
|
+
outwardAllowed = allowed;
|
|
55
|
+
}
|
|
56
|
+
export function assertOutwardAllowed(targets) {
|
|
57
|
+
if (outwardAllowed === undefined)
|
|
58
|
+
return;
|
|
59
|
+
for (const el of targets) {
|
|
60
|
+
if (null === el)
|
|
61
|
+
continue;
|
|
62
|
+
const kind = outwardActionOf(dangerousActionContext(el), getRole(el));
|
|
63
|
+
if (kind !== undefined && !outwardAllowed.includes(kind))
|
|
64
|
+
throw new Error(outwardRefusal(kind));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ActionTarget } from '../dom/realm.js';
|
|
2
|
+
export declare function isAutofillSafeTarget(el: Element, ua?: string): boolean;
|
|
2
3
|
export declare function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string): boolean;
|
|
3
4
|
export declare function assertNotRichText(el: ActionTarget, action: string): void;
|
|
4
5
|
export declare function assertEditable(el: HTMLInputElement | HTMLTextAreaElement, action: string): void;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { captureValueSetter } from '../patching/capture-method.js';
|
|
2
2
|
import { isHtmlElement, valuePrototypeOf } from '../dom/realm.js';
|
|
3
|
+
const PASSWORD_AUTOCOMPLETE = /\b(current|new)-password\b/;
|
|
4
|
+
const NOT_WEBKIT = /Chrome|Chromium|Edg\/|Firefox|Gecko\//;
|
|
5
|
+
export function isAutofillSafeTarget(el, ua = navigator.userAgent) {
|
|
6
|
+
if (!(el instanceof HTMLInputElement))
|
|
7
|
+
return false;
|
|
8
|
+
const password = 'password' === el.type.toLowerCase() ||
|
|
9
|
+
PASSWORD_AUTOCOMPLETE.test(el.getAttribute('autocomplete') ?? '');
|
|
10
|
+
if (!password || NOT_WEBKIT.test(ua))
|
|
11
|
+
return false;
|
|
12
|
+
return 600 <= Number(/AppleWebKit\/(\d+)/.exec(ua)?.[1] ?? 0);
|
|
13
|
+
}
|
|
3
14
|
export function setNativeValue(el, value) {
|
|
4
15
|
const proto = valuePrototypeOf(el);
|
|
5
16
|
const setter = proto === undefined ? undefined : captureValueSetter(proto);
|
|
@@ -122,6 +122,9 @@ function readState(ref, store, path, depth) {
|
|
|
122
122
|
const names = storeNames();
|
|
123
123
|
if (path !== undefined || depth !== undefined) {
|
|
124
124
|
const rawStores = readStoresRaw(store);
|
|
125
|
+
if (store !== undefined && !Object.hasOwn(rawStores, store)) {
|
|
126
|
+
return { store, path, found: false, value: null, storeNames: names };
|
|
127
|
+
}
|
|
125
128
|
const base = store !== undefined ? rawStores[store] : { stores: rawStores, storeNames: names };
|
|
126
129
|
const selection = path !== undefined ? selectPath(base, path) : { found: true, value: base };
|
|
127
130
|
const selected = selection.found && depth !== undefined ? capDepth(selection.value, depth) : selection.value;
|
|
@@ -220,6 +223,7 @@ export function createCommandRegistry() {
|
|
|
220
223
|
reg.set(ReticleCommand.STORAGE_READ, (args) => readStorage(str(args['area'])));
|
|
221
224
|
reg.set(ReticleCommand.CAPABILITIES, () => getCapabilities());
|
|
222
225
|
reg.set(ReticleCommand.CAPTURE, (args) => captureDesktopWindow(true === args['fullPage']));
|
|
226
|
+
reg.set(ReticleCommand.RENDER, () => import('../dom/render-page.js').then((m) => m.renderPage()));
|
|
223
227
|
reg.set(ReticleCommand.SCROLL, (args) => {
|
|
224
228
|
const dy = args['dy'];
|
|
225
229
|
const fraction = args['fraction'];
|
package/dist/dom/a11y.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readTestId } from './addressing/testid-attr.js';
|
|
2
|
+
import { DEFAULT_TESTID_ATTR, ElementState, REDACTED_VALUE, } from '@reticlehq/core';
|
|
2
3
|
import { isButton, isHtmlElement, isImage, isInput, isMeter, isOutput, isProgress, isSelect, isTextArea, } from './realm.js';
|
|
3
4
|
import { refs } from './addressing/refs.js';
|
|
4
5
|
import { capturedRootOf } from './shadow-registry.js';
|
|
@@ -31,9 +32,11 @@ const NAME_FROM_CONTENT = new Set([
|
|
|
31
32
|
'status',
|
|
32
33
|
'alert',
|
|
33
34
|
]);
|
|
34
|
-
const INPUT_TEXT_TYPES = new Set(['text', 'email', 'tel', 'url', '
|
|
35
|
+
const INPUT_TEXT_TYPES = new Set(['text', 'email', 'tel', 'url', 'password', '']);
|
|
35
36
|
function inputRole(input) {
|
|
36
37
|
const type = input.type.toLowerCase();
|
|
38
|
+
if ('search' === type)
|
|
39
|
+
return 'searchbox';
|
|
37
40
|
if (INPUT_TEXT_TYPES.has(type))
|
|
38
41
|
return 'textbox';
|
|
39
42
|
if ('checkbox' === type)
|
|
@@ -274,6 +277,7 @@ export function getStates(el, visible = isVisible(el)) {
|
|
|
274
277
|
states.push(ElementState.FOCUSED);
|
|
275
278
|
return states;
|
|
276
279
|
}
|
|
280
|
+
const SENSITIVE_AUTOCOMPLETE = /\b(cc-|current-password|new-password|one-time-code)/i;
|
|
277
281
|
export function isSensitiveField(el) {
|
|
278
282
|
if (!isInput(el) && !isTextArea(el) && !isSelect(el)) {
|
|
279
283
|
return false;
|
|
@@ -282,10 +286,11 @@ export function isSensitiveField(el) {
|
|
|
282
286
|
const identifiers = [
|
|
283
287
|
el.getAttribute('name') ?? '',
|
|
284
288
|
el.id,
|
|
285
|
-
el
|
|
289
|
+
readTestId(el) ?? '',
|
|
290
|
+
el.getAttribute(DEFAULT_TESTID_ATTR) ?? '',
|
|
286
291
|
el.getAttribute('aria-label') ?? '',
|
|
287
292
|
];
|
|
288
|
-
const sensitiveAutocomplete =
|
|
293
|
+
const sensitiveAutocomplete = SENSITIVE_AUTOCOMPLETE.test(autocomplete);
|
|
289
294
|
return ((isInput(el) && 'password' === el.type.toLowerCase()) ||
|
|
290
295
|
sensitiveAutocomplete ||
|
|
291
296
|
identifiers.some(isSensitiveKey));
|
|
@@ -293,7 +298,7 @@ export function isSensitiveField(el) {
|
|
|
293
298
|
export function getValue(el) {
|
|
294
299
|
if (isInput(el) || isTextArea(el) || isSelect(el)) {
|
|
295
300
|
if (isSensitiveField(el))
|
|
296
|
-
return REDACTED_VALUE;
|
|
301
|
+
return '' === el.value ? '' : REDACTED_VALUE;
|
|
297
302
|
return el.value;
|
|
298
303
|
}
|
|
299
304
|
const valueNow = el.getAttribute('aria-valuenow');
|
|
@@ -309,22 +314,29 @@ function hiddenInsideClosedDetails(el) {
|
|
|
309
314
|
const summary = Array.from(details.children).find((child) => HTML_SUMMARY_TAG === child.localName);
|
|
310
315
|
return summary === undefined || !summary.contains(el);
|
|
311
316
|
}
|
|
312
|
-
function
|
|
313
|
-
if ('
|
|
317
|
+
function stalledFade(el) {
|
|
318
|
+
if (!el.ownerDocument.hidden || !('getAnimations' in el))
|
|
319
|
+
return false;
|
|
320
|
+
return el.getAnimations().some((a) => 'running' === a.playState || a.pending);
|
|
321
|
+
}
|
|
322
|
+
const OVERLAY_ARIA_HIDDEN = 'data-aria-hidden';
|
|
323
|
+
function hiddenByAnOverlay(el) {
|
|
324
|
+
return el.hasAttribute(OVERLAY_ARIA_HIDDEN);
|
|
325
|
+
}
|
|
326
|
+
function selfHidden(el, style) {
|
|
327
|
+
if ('true' === el.getAttribute('aria-hidden') && !hiddenByAnOverlay(el))
|
|
314
328
|
return true;
|
|
315
329
|
if (isHtmlElement(el) && el.hidden)
|
|
316
330
|
return true;
|
|
317
331
|
if (hiddenInsideClosedDetails(el))
|
|
318
332
|
return true;
|
|
319
|
-
|
|
320
|
-
if (view !== null) {
|
|
321
|
-
const style = view.getComputedStyle(el);
|
|
333
|
+
if (style !== null) {
|
|
322
334
|
if ('none' === style.display ||
|
|
323
335
|
'hidden' === style.visibility ||
|
|
324
336
|
'collapse' === style.visibility) {
|
|
325
337
|
return true;
|
|
326
338
|
}
|
|
327
|
-
if (0 === Number.parseFloat(style.opacity || '1'))
|
|
339
|
+
if (0 === Number.parseFloat(style.opacity || '1') && !stalledFade(el))
|
|
328
340
|
return true;
|
|
329
341
|
}
|
|
330
342
|
return false;
|
|
@@ -351,13 +363,252 @@ function parentAcrossShadowBoundary(el) {
|
|
|
351
363
|
const host = el.getRootNode().host;
|
|
352
364
|
return host ?? null;
|
|
353
365
|
}
|
|
366
|
+
const CLIPPING_OVERFLOW = new Set(['hidden', 'clip']);
|
|
367
|
+
const SCROLLING_OVERFLOW = new Set(['auto', 'scroll']);
|
|
368
|
+
const NON_CLIPPING_DISPLAY = new Set([
|
|
369
|
+
'contents',
|
|
370
|
+
'table-row',
|
|
371
|
+
'table-row-group',
|
|
372
|
+
'table-header-group',
|
|
373
|
+
'table-footer-group',
|
|
374
|
+
'table-column',
|
|
375
|
+
'table-column-group',
|
|
376
|
+
]);
|
|
377
|
+
const TRANSFORM_CONTAINING_BLOCK_PROPERTIES = new Set([
|
|
378
|
+
'transform',
|
|
379
|
+
'translate',
|
|
380
|
+
'rotate',
|
|
381
|
+
'scale',
|
|
382
|
+
'perspective',
|
|
383
|
+
]);
|
|
384
|
+
const CONTAINING_BLOCK_PROPERTIES = new Set([
|
|
385
|
+
...TRANSFORM_CONTAINING_BLOCK_PROPERTIES,
|
|
386
|
+
'filter',
|
|
387
|
+
'backdrop-filter',
|
|
388
|
+
]);
|
|
389
|
+
const CONTAINING_BLOCK_CONTAIN = new Set(['layout', 'paint', 'strict', 'content']);
|
|
390
|
+
const REPLACED_INLINE_TAGS = new Set([
|
|
391
|
+
'img',
|
|
392
|
+
'video',
|
|
393
|
+
'audio',
|
|
394
|
+
'canvas',
|
|
395
|
+
'iframe',
|
|
396
|
+
'embed',
|
|
397
|
+
'object',
|
|
398
|
+
'input',
|
|
399
|
+
'textarea',
|
|
400
|
+
'select',
|
|
401
|
+
]);
|
|
402
|
+
const visibilityInfoMemos = new WeakMap();
|
|
403
|
+
function visibilityInfoMemo(memo) {
|
|
404
|
+
if (memo === undefined)
|
|
405
|
+
return new Map();
|
|
406
|
+
let info = visibilityInfoMemos.get(memo);
|
|
407
|
+
if (info === undefined) {
|
|
408
|
+
info = new Map();
|
|
409
|
+
visibilityInfoMemos.set(memo, info);
|
|
410
|
+
}
|
|
411
|
+
return info;
|
|
412
|
+
}
|
|
413
|
+
function ownVisibilityInfo(el, memo) {
|
|
414
|
+
let info = memo.get(el);
|
|
415
|
+
if (info === undefined) {
|
|
416
|
+
const style = el.ownerDocument.defaultView?.getComputedStyle(el) ?? null;
|
|
417
|
+
if (selfHidden(el, style)) {
|
|
418
|
+
info = {
|
|
419
|
+
hidden: true,
|
|
420
|
+
style,
|
|
421
|
+
parent: null,
|
|
422
|
+
clipX: false,
|
|
423
|
+
clipY: false,
|
|
424
|
+
scrollX: false,
|
|
425
|
+
scrollY: false,
|
|
426
|
+
hasClipping: false,
|
|
427
|
+
};
|
|
428
|
+
memo.set(el, info);
|
|
429
|
+
return info;
|
|
430
|
+
}
|
|
431
|
+
const parent = parentAcrossShadowBoundary(el);
|
|
432
|
+
const inherited = null === parent ? undefined : ownVisibilityInfo(parent, memo);
|
|
433
|
+
const [overflowX, overflowY] = overflowAxes(style);
|
|
434
|
+
const root = el.ownerDocument.documentElement;
|
|
435
|
+
const viewportOverflow = el === root ||
|
|
436
|
+
(el === el.ownerDocument.body &&
|
|
437
|
+
overflowAxes(ownVisibilityInfo(root, memo).style).every((axis) => 'visible' === axis));
|
|
438
|
+
const hasClipBox = !viewportOverflow &&
|
|
439
|
+
!NON_CLIPPING_DISPLAY.has(style?.display ?? '') &&
|
|
440
|
+
!(isHtmlElement(el) && 'inline' === style?.display);
|
|
441
|
+
const clipX = hasClipBox && CLIPPING_OVERFLOW.has(overflowX);
|
|
442
|
+
const clipY = hasClipBox && CLIPPING_OVERFLOW.has(overflowY);
|
|
443
|
+
info = {
|
|
444
|
+
hidden: true === inherited?.hidden,
|
|
445
|
+
style,
|
|
446
|
+
parent,
|
|
447
|
+
clipX,
|
|
448
|
+
clipY,
|
|
449
|
+
scrollX: hasClipBox && SCROLLING_OVERFLOW.has(overflowX),
|
|
450
|
+
scrollY: hasClipBox && SCROLLING_OVERFLOW.has(overflowY),
|
|
451
|
+
hasClipping: clipX || clipY || true === inherited?.hasClipping,
|
|
452
|
+
};
|
|
453
|
+
memo.set(el, info);
|
|
454
|
+
}
|
|
455
|
+
return info;
|
|
456
|
+
}
|
|
457
|
+
function overflowAxes(style) {
|
|
458
|
+
const shorthand = style?.overflow ?? '';
|
|
459
|
+
const [x = 'visible', y = x] = ('' === shorthand ? 'visible' : shorthand).split(/\s+/);
|
|
460
|
+
return [
|
|
461
|
+
null === style || '' === style.overflowX ? x : style.overflowX,
|
|
462
|
+
null === style || '' === style.overflowY ? y : style.overflowY,
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
function positionedContainingBlock(el, fixed, memo) {
|
|
466
|
+
for (let parent = ownVisibilityInfo(el, memo).parent; parent !== null; parent = ownVisibilityInfo(parent, memo).parent) {
|
|
467
|
+
const style = ownVisibilityInfo(parent, memo).style;
|
|
468
|
+
if (null === style || 'contents' === style.display)
|
|
469
|
+
continue;
|
|
470
|
+
if (!fixed && '' !== style.position && 'static' !== style.position)
|
|
471
|
+
return parent;
|
|
472
|
+
const transformable = !('inline' === style.display &&
|
|
473
|
+
isHtmlElement(parent) &&
|
|
474
|
+
!REPLACED_INLINE_TAGS.has(parent.localName)) &&
|
|
475
|
+
'table-column' !== style.display &&
|
|
476
|
+
'table-column-group' !== style.display;
|
|
477
|
+
const applies = (property) => transformable || !TRANSFORM_CONTAINING_BLOCK_PROPERTIES.has(property);
|
|
478
|
+
if ([...CONTAINING_BLOCK_PROPERTIES].some((property) => {
|
|
479
|
+
const value = style.getPropertyValue(property);
|
|
480
|
+
return applies(property) && '' !== value && 'none' !== value;
|
|
481
|
+
}) ||
|
|
482
|
+
style.willChange
|
|
483
|
+
.split(',')
|
|
484
|
+
.some((property) => CONTAINING_BLOCK_PROPERTIES.has(property.trim()) && applies(property.trim())) ||
|
|
485
|
+
(transformable &&
|
|
486
|
+
!NON_CLIPPING_DISPLAY.has(style.display) &&
|
|
487
|
+
(style.contain.split(/\s+/).some((value) => CONTAINING_BLOCK_CONTAIN.has(value)) ||
|
|
488
|
+
'auto' === style.contentVisibility)))
|
|
489
|
+
return parent;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
function clippingBox(el, info) {
|
|
494
|
+
if (info.clipBox !== undefined)
|
|
495
|
+
return info.clipBox;
|
|
496
|
+
const rect = el.getBoundingClientRect();
|
|
497
|
+
const style = info.style;
|
|
498
|
+
const px = (value) => Number.parseFloat(value ?? '') || 0;
|
|
499
|
+
const border = (value, kind) => kind === undefined || '' === kind || 'none' === kind || 'hidden' === kind ? 0 : px(value);
|
|
500
|
+
const scaleX = isHtmlElement(el) && el.offsetWidth > 0 ? rect.width / el.offsetWidth : 1;
|
|
501
|
+
const scaleY = isHtmlElement(el) && el.offsetHeight > 0 ? rect.height / el.offsetHeight : 1;
|
|
502
|
+
const left = isHtmlElement(el) && el.offsetWidth > 0
|
|
503
|
+
? rect.left + el.clientLeft * scaleX
|
|
504
|
+
: rect.left + border(style?.borderLeftWidth, style?.borderLeftStyle);
|
|
505
|
+
const top = isHtmlElement(el) && el.offsetHeight > 0
|
|
506
|
+
? rect.top + el.clientTop * scaleY
|
|
507
|
+
: rect.top + border(style?.borderTopWidth, style?.borderTopStyle);
|
|
508
|
+
const right = isHtmlElement(el) && el.offsetWidth > 0
|
|
509
|
+
? left + el.clientWidth * scaleX
|
|
510
|
+
: rect.right - border(style?.borderRightWidth, style?.borderRightStyle);
|
|
511
|
+
const bottom = isHtmlElement(el) && el.offsetHeight > 0
|
|
512
|
+
? top + el.clientHeight * scaleY
|
|
513
|
+
: rect.bottom - border(style?.borderBottomWidth, style?.borderBottomStyle);
|
|
514
|
+
const margin = (style?.overflowClipMargin ?? '').split(/\s+/);
|
|
515
|
+
const outset = px(margin.at(-1));
|
|
516
|
+
const borderBox = margin.includes('border-box');
|
|
517
|
+
const contentBox = margin.includes('content-box');
|
|
518
|
+
const [x, y] = overflowAxes(style);
|
|
519
|
+
const clipLeft = 'clip' === x
|
|
520
|
+
? (borderBox ? rect.left : left + (contentBox ? px(style?.paddingLeft) * scaleX : 0)) -
|
|
521
|
+
outset * scaleX
|
|
522
|
+
: left;
|
|
523
|
+
const clipRight = 'clip' === x
|
|
524
|
+
? (borderBox ? rect.right : right - (contentBox ? px(style?.paddingRight) * scaleX : 0)) +
|
|
525
|
+
outset * scaleX
|
|
526
|
+
: right;
|
|
527
|
+
const clipTop = 'clip' === y
|
|
528
|
+
? (borderBox ? rect.top : top + (contentBox ? px(style?.paddingTop) * scaleY : 0)) -
|
|
529
|
+
outset * scaleY
|
|
530
|
+
: top;
|
|
531
|
+
const clipBottom = 'clip' === y
|
|
532
|
+
? (borderBox ? rect.bottom : bottom - (contentBox ? px(style?.paddingBottom) * scaleY : 0)) +
|
|
533
|
+
outset * scaleY
|
|
534
|
+
: bottom;
|
|
535
|
+
info.clipBox = {
|
|
536
|
+
left: clipLeft,
|
|
537
|
+
right: clipRight,
|
|
538
|
+
top: clipTop,
|
|
539
|
+
bottom: clipBottom,
|
|
540
|
+
width: clipRight - clipLeft,
|
|
541
|
+
height: clipBottom - clipTop,
|
|
542
|
+
};
|
|
543
|
+
return info.clipBox;
|
|
544
|
+
}
|
|
545
|
+
function scrolledInto(b, port, alongX, alongY) {
|
|
546
|
+
const axis = (lo, hi, size, from, to) => {
|
|
547
|
+
if (hi > from && lo < to)
|
|
548
|
+
return [lo, hi];
|
|
549
|
+
const span = Math.min(size, to - from);
|
|
550
|
+
return lo >= to ? [to - span, to] : [from, from + span];
|
|
551
|
+
};
|
|
552
|
+
const [left, right] = alongX
|
|
553
|
+
? axis(b.left, b.right, b.width, port.left, port.right)
|
|
554
|
+
: [b.left, b.right];
|
|
555
|
+
const [top, bottom] = alongY
|
|
556
|
+
? axis(b.top, b.bottom, b.height, port.top, port.bottom)
|
|
557
|
+
: [b.top, b.bottom];
|
|
558
|
+
return { left, right, top, bottom, width: right - left, height: bottom - top };
|
|
559
|
+
}
|
|
560
|
+
function visibleBounds(el, memo, initial, throughScrollers = false) {
|
|
561
|
+
let bounds = initial;
|
|
562
|
+
const target = ownVisibilityInfo(el, memo);
|
|
563
|
+
if (target.hidden)
|
|
564
|
+
return null;
|
|
565
|
+
if (null === target.parent || !ownVisibilityInfo(target.parent, memo).hasClipping)
|
|
566
|
+
return bounds;
|
|
567
|
+
let clipFrom;
|
|
568
|
+
for (let current = el; current !== null; current = ownVisibilityInfo(current, memo).parent) {
|
|
569
|
+
const info = ownVisibilityInfo(current, memo);
|
|
570
|
+
if (info.hidden)
|
|
571
|
+
return null;
|
|
572
|
+
if (current === clipFrom)
|
|
573
|
+
clipFrom = undefined;
|
|
574
|
+
const style = info.style;
|
|
575
|
+
if (throughScrollers &&
|
|
576
|
+
current !== el &&
|
|
577
|
+
undefined === clipFrom &&
|
|
578
|
+
(info.scrollX || info.scrollY)) {
|
|
579
|
+
bounds ??= el.getBoundingClientRect();
|
|
580
|
+
if (bounds.width > 0 && bounds.height > 0) {
|
|
581
|
+
bounds = scrolledInto(bounds, clippingBox(current, info), info.scrollX, info.scrollY);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (current !== el && undefined === clipFrom && (info.clipX || info.clipY)) {
|
|
585
|
+
bounds ??= el.getBoundingClientRect();
|
|
586
|
+
if (bounds.width > 0 && bounds.height > 0) {
|
|
587
|
+
const clip = clippingBox(current, info);
|
|
588
|
+
const left = info.clipX ? Math.max(bounds.left, clip.left) : bounds.left;
|
|
589
|
+
const right = info.clipX ? Math.min(bounds.right, clip.right) : bounds.right;
|
|
590
|
+
const top = info.clipY ? Math.max(bounds.top, clip.top) : bounds.top;
|
|
591
|
+
const bottom = info.clipY ? Math.min(bounds.bottom, clip.bottom) : bounds.bottom;
|
|
592
|
+
if (right <= left || bottom <= top)
|
|
593
|
+
return null;
|
|
594
|
+
bounds = { left, right, top, bottom, width: right - left, height: bottom - top };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (clipFrom === undefined && ('absolute' === style?.position || 'fixed' === style?.position)) {
|
|
598
|
+
clipFrom = positionedContainingBlock(current, 'fixed' === style?.position, memo);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return bounds;
|
|
602
|
+
}
|
|
354
603
|
export function isInViewport(el, memo) {
|
|
355
604
|
if (!isVisible(el, memo))
|
|
356
605
|
return false;
|
|
357
606
|
const view = el.ownerDocument.defaultView;
|
|
358
607
|
if (null === view)
|
|
359
608
|
return false;
|
|
360
|
-
const r = el.getBoundingClientRect();
|
|
609
|
+
const r = visibleBounds(el, visibilityInfoMemo(memo), el.getBoundingClientRect());
|
|
610
|
+
if (null === r || undefined === r)
|
|
611
|
+
return false;
|
|
361
612
|
if (r.width <= 0 || r.height <= 0)
|
|
362
613
|
return false;
|
|
363
614
|
return r.bottom > 0 && r.right > 0 && r.top < view.innerHeight && r.left < view.innerWidth;
|
|
@@ -368,8 +619,7 @@ export function isVisible(el, memo) {
|
|
|
368
619
|
const cached = memo?.get(el);
|
|
369
620
|
if (cached !== undefined)
|
|
370
621
|
return cached;
|
|
371
|
-
const
|
|
372
|
-
const result = !selfHidden(el) && (null === parent || isVisible(parent, memo));
|
|
622
|
+
const result = visibleBounds(el, visibilityInfoMemo(memo), undefined, true) !== null;
|
|
373
623
|
if (memo !== undefined)
|
|
374
624
|
memo.set(el, result);
|
|
375
625
|
return result;
|