@reticlehq/browser 3.7.0 → 3.8.1
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 +10 -0
- package/dist/actions/action-report.js +59 -14
- package/dist/actions/actions-dom.d.ts +4 -0
- package/dist/actions/actions-dom.js +20 -4
- package/dist/actions/actions.d.ts +1 -0
- package/dist/actions/actions.js +94 -46
- package/dist/actions/appeared-text.js +2 -2
- package/dist/actions/click-target.d.ts +1 -0
- package/dist/actions/click-target.js +14 -0
- package/dist/actions/danger-context.d.ts +2 -1
- package/dist/actions/danger-context.js +43 -17
- package/dist/actions/scroll.d.ts +2 -0
- package/dist/actions/scroll.js +10 -2
- package/dist/activate.d.ts +14 -0
- package/dist/activate.js +43 -0
- package/dist/busy.d.ts +3 -0
- package/dist/busy.js +42 -0
- package/dist/commands/commands.js +45 -17
- package/dist/connect-options.d.ts +12 -1
- package/dist/connect-options.js +18 -1
- package/dist/dom/a11y.d.ts +0 -2
- package/dist/dom/a11y.js +103 -363
- package/dist/dom/chart.d.ts +3 -2
- package/dist/dom/chart.js +3 -2
- package/dist/dom/dom-ignore.d.ts +6 -0
- package/dist/dom/dom-ignore.js +60 -5
- package/dist/dom/layout-audit.d.ts +15 -0
- package/dist/dom/layout-audit.js +208 -0
- package/dist/dom/paint-context.d.ts +15 -0
- package/dist/dom/paint-context.js +78 -0
- package/dist/dom/query-finders.d.ts +6 -0
- package/dist/dom/query-finders.js +216 -0
- package/dist/dom/query.d.ts +1 -1
- package/dist/dom/query.js +154 -227
- package/dist/dom/shadow-registry.d.ts +1 -0
- package/dist/dom/shadow-registry.js +27 -0
- package/dist/dom/snapshot.d.ts +5 -0
- package/dist/dom/snapshot.js +87 -35
- package/dist/dom/stable-selector.d.ts +1 -0
- package/dist/dom/stable-selector.js +19 -0
- package/dist/dom/text-match.d.ts +12 -0
- package/dist/dom/text-match.js +115 -0
- package/dist/dom/theme.d.ts +1 -0
- package/dist/dom/theme.js +21 -9
- package/dist/dom/virtualized.js +5 -1
- package/dist/dom/visibility.d.ts +7 -0
- package/dist/dom/visibility.js +322 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/observers/blind-spots.js +3 -2
- package/dist/observers/canvas.d.ts +18 -0
- package/dist/observers/canvas.js +76 -0
- package/dist/observers/dom.js +68 -19
- package/dist/observers/field.d.ts +11 -0
- package/dist/observers/field.js +117 -13
- package/dist/observers/install-all.js +4 -0
- package/dist/observers/media.d.ts +2 -0
- package/dist/observers/media.js +32 -0
- package/dist/observers/net-detail/network-body.d.ts +2 -0
- package/dist/observers/net-detail/network-body.js +36 -9
- package/dist/observers/net-detail/network-event-stream.d.ts +5 -0
- package/dist/observers/net-detail/network-event-stream.js +55 -0
- package/dist/observers/net-detail/network-frames.d.ts +2 -0
- package/dist/observers/net-detail/network-frames.js +39 -0
- package/dist/observers/net-detail/network-response-body.d.ts +3 -0
- package/dist/observers/net-detail/network-response-body.js +97 -0
- package/dist/observers/network.js +111 -101
- package/dist/observers/route.d.ts +2 -0
- package/dist/observers/route.js +33 -4
- package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -5
- package/dist/presenter/chrome/presenter-report-copy.js +0 -14
- package/dist/presenter/hud-telemetry.js +1 -1
- package/dist/presenter/presenter-chat-views.d.ts +3 -4
- package/dist/presenter/presenter-chat-views.js +47 -20
- package/dist/presenter/presenter-config.d.ts +1 -0
- package/dist/presenter/presenter-config.js +1 -0
- package/dist/presenter/presenter-frame.d.ts +3 -2
- package/dist/presenter/presenter-frame.js +24 -15
- package/dist/presenter/presenter-harness-row.d.ts +4 -15
- package/dist/presenter/presenter-harness-row.js +11 -50
- package/dist/presenter/presenter-report-styles.js +0 -17
- package/dist/presenter/presenter-report.d.ts +1 -2
- package/dist/presenter/presenter-report.js +4 -54
- package/dist/presenter/presenter-run-state.js +2 -1
- package/dist/presenter/presenter-settings.d.ts +2 -3
- package/dist/presenter/presenter-settings.js +14 -17
- package/dist/presenter/presenter-shell.d.ts +6 -4
- package/dist/presenter/presenter-shell.js +37 -7
- package/dist/presenter/presenter.js +32 -11
- package/dist/registry/store-adapters.d.ts +5 -2
- package/dist/registry/store-adapters.js +40 -10
- package/dist/reticle-inject.js +700 -645
- package/dist/reticle.d.ts +4 -2
- package/dist/reticle.js +36 -6
- package/dist/security/page-location.d.ts +2 -0
- package/dist/security/page-location.js +7 -0
- package/dist/security/serialization.js +1 -1
- package/dist/storage-keys.d.ts +1 -0
- package/dist/storage-keys.js +1 -0
- package/dist/timers/native/native-timers.d.ts +1 -0
- package/dist/timers/native/native-timers.js +5 -0
- package/dist/transport/transport.d.ts +1 -0
- package/dist/transport/transport.js +18 -0
- package/package.json +6 -2
package/dist/dom/snapshot.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { ElementState, SnapshotMode } from '@reticlehq/core';
|
|
2
2
|
import { capturedRootOf } from './shadow-registry.js';
|
|
3
3
|
import { isElement, isFrame } from './realm.js';
|
|
4
|
-
import { getAccessibleName, getRole, getStates, getValue
|
|
4
|
+
import { getAccessibleName, getRole, getStates, getValue } from './a11y.js';
|
|
5
|
+
import { isVisible } from './visibility.js';
|
|
5
6
|
import { refs } from './addressing/refs.js';
|
|
7
|
+
import { readTestId } from './addressing/testid-attr.js';
|
|
8
|
+
import { identifyComponent } from '../registry/stores/adapters.js';
|
|
9
|
+
import { auditLayout } from './layout-audit.js';
|
|
10
|
+
import { pageRoute } from '../security/page-location.js';
|
|
6
11
|
import { isIgnored, isReticleOverlay } from './dom-ignore.js';
|
|
7
|
-
const INTERACTIVE = new Set([
|
|
12
|
+
export const INTERACTIVE = new Set([
|
|
8
13
|
'button',
|
|
9
14
|
'link',
|
|
10
15
|
'textbox',
|
|
@@ -23,8 +28,9 @@ const INTERACTIVE = new Set([
|
|
|
23
28
|
const SKIP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'head', 'meta', 'link']);
|
|
24
29
|
const ANNOUNCE_ROLES = new Set(['alert', 'status']);
|
|
25
30
|
function announces(el, role) {
|
|
26
|
-
if (ANNOUNCE_ROLES.has(role))
|
|
31
|
+
if (ANNOUNCE_ROLES.has(role)) {
|
|
27
32
|
return true;
|
|
33
|
+
}
|
|
28
34
|
const live = el.getAttribute('aria-live');
|
|
29
35
|
return live !== null && 'off' !== live;
|
|
30
36
|
}
|
|
@@ -56,30 +62,36 @@ function readingParts(el) {
|
|
|
56
62
|
function isInlineIn(el) {
|
|
57
63
|
const view = el.ownerDocument.defaultView;
|
|
58
64
|
const parent = el.parentElement;
|
|
59
|
-
if (null === view || null === parent)
|
|
65
|
+
if (null === view || null === parent) {
|
|
60
66
|
return false;
|
|
67
|
+
}
|
|
61
68
|
const around = view.getComputedStyle(parent).display;
|
|
62
|
-
if (around.includes('flex') || around.includes('grid'))
|
|
69
|
+
if (around.includes('flex') || around.includes('grid')) {
|
|
63
70
|
return false;
|
|
71
|
+
}
|
|
64
72
|
return 'inline' === view.getComputedStyle(el).display;
|
|
65
73
|
}
|
|
66
74
|
function foldsIntoParent(el) {
|
|
67
|
-
if (skipEarly(el) || !isInlineIn(el))
|
|
75
|
+
if (skipEarly(el) || !isInlineIn(el)) {
|
|
68
76
|
return false;
|
|
77
|
+
}
|
|
69
78
|
const role = getRole(el);
|
|
70
79
|
return !INTERACTIVE.has(role) && !announces(el, role) && 0 === getAccessibleName(el).length;
|
|
71
80
|
}
|
|
72
81
|
export const MAX_UNREAD_BRANCHES = 50;
|
|
73
82
|
function hiddenByMarkup(el) {
|
|
74
|
-
if ('true' === el.getAttribute('aria-hidden'))
|
|
83
|
+
if ('true' === el.getAttribute('aria-hidden')) {
|
|
75
84
|
return true;
|
|
85
|
+
}
|
|
76
86
|
return el instanceof HTMLElement && el.hidden;
|
|
77
87
|
}
|
|
78
88
|
function skipEarly(el) {
|
|
79
|
-
if (SKIP_TAGS.has(el.tagName.toLowerCase()))
|
|
89
|
+
if (SKIP_TAGS.has(el.tagName.toLowerCase())) {
|
|
80
90
|
return true;
|
|
81
|
-
|
|
91
|
+
}
|
|
92
|
+
if (isIgnored(el)) {
|
|
82
93
|
return true;
|
|
94
|
+
}
|
|
83
95
|
return hiddenByMarkup(el);
|
|
84
96
|
}
|
|
85
97
|
function stateSuffix(el) {
|
|
@@ -90,7 +102,7 @@ function stateSuffix(el) {
|
|
|
90
102
|
s === ElementState.FOCUSED);
|
|
91
103
|
return states.length > 0 ? ` [${states.join(',')}]` : '';
|
|
92
104
|
}
|
|
93
|
-
function formatLine(el, depth, role, name, layout, ownText = '') {
|
|
105
|
+
function formatLine(el, depth, role, name, layout, ownText = '', withTestId = false) {
|
|
94
106
|
const indent = ' '.repeat(depth);
|
|
95
107
|
const value = getValue(el);
|
|
96
108
|
const label = name;
|
|
@@ -99,14 +111,27 @@ function formatLine(el, depth, role, name, layout, ownText = '') {
|
|
|
99
111
|
const valuePart = value !== undefined && value.length > 0 ? ` [value="${value}"]` : '';
|
|
100
112
|
const layoutPart = layout.length > 0 ? ` [${layout}]` : '';
|
|
101
113
|
const textPart = ownText.length > 0 ? ` [text="${ownText}"]` : '';
|
|
102
|
-
|
|
114
|
+
const testid = withTestId ? readTestId(el) : null;
|
|
115
|
+
const info = withTestId ? identifyComponent(el) : null;
|
|
116
|
+
const component = null === testid ? info?.componentStack[0] : undefined;
|
|
117
|
+
const where = info?.source;
|
|
118
|
+
let who = '';
|
|
119
|
+
if (null !== testid) {
|
|
120
|
+
who = ` [testid=${testid}]`;
|
|
121
|
+
}
|
|
122
|
+
else if (component !== undefined) {
|
|
123
|
+
who = ` [component=${component}]`;
|
|
124
|
+
}
|
|
125
|
+
const idPart = `${who}${where === undefined ? '' : ` [src=${where.file}:${String(where.line)}]`}`;
|
|
126
|
+
return `${indent}- ${role}${namePart}${refPart}${valuePart}${textPart}${layoutPart}${stateSuffix(el)}${idPart}`;
|
|
103
127
|
}
|
|
104
128
|
function formatTextLine(depth, text) {
|
|
105
129
|
return `${' '.repeat(depth)}- text "${text}"`;
|
|
106
130
|
}
|
|
107
131
|
function layoutSignature(style) {
|
|
108
|
-
if (null === style)
|
|
132
|
+
if (null === style) {
|
|
109
133
|
return '';
|
|
134
|
+
}
|
|
110
135
|
const display = style.display;
|
|
111
136
|
if ('grid' === display || 'inline-grid' === display) {
|
|
112
137
|
const cols = style.gridTemplateColumns;
|
|
@@ -127,13 +152,15 @@ function recordUnread(branches, ctx) {
|
|
|
127
152
|
function pierceChildren(parent) {
|
|
128
153
|
const out = [...parent.children];
|
|
129
154
|
const shadow = parent.shadowRoot ?? capturedRootOf(parent);
|
|
130
|
-
if (shadow !== null)
|
|
155
|
+
if (shadow !== null) {
|
|
131
156
|
out.push(...shadow.children);
|
|
157
|
+
}
|
|
132
158
|
if (isFrame(parent)) {
|
|
133
159
|
try {
|
|
134
160
|
const body = parent.contentDocument?.body;
|
|
135
|
-
if (body !== null && body !== undefined)
|
|
161
|
+
if (body !== null && body !== undefined) {
|
|
136
162
|
out.push(...body.children);
|
|
163
|
+
}
|
|
137
164
|
}
|
|
138
165
|
catch {
|
|
139
166
|
}
|
|
@@ -146,8 +173,9 @@ function visit(child, depth, ctx, inLive) {
|
|
|
146
173
|
!isIgnored(child) &&
|
|
147
174
|
!(child instanceof HTMLElement && child.hidden);
|
|
148
175
|
if (!stampedOnly && skipEarly(child)) {
|
|
149
|
-
if (hiddenByMarkup(child))
|
|
176
|
+
if (hiddenByMarkup(child)) {
|
|
150
177
|
ctx.hiddenSkipped += 1;
|
|
178
|
+
}
|
|
151
179
|
return;
|
|
152
180
|
}
|
|
153
181
|
const view = child.ownerDocument.defaultView;
|
|
@@ -170,13 +198,14 @@ function visit(child, depth, ctx, inLive) {
|
|
|
170
198
|
const actionable = interactive;
|
|
171
199
|
const meaningful = actionable || role !== 'generic' || name.length > 0 || text.length > 0 || layout.length > 0;
|
|
172
200
|
const include = lean ? actionable : meaningful;
|
|
173
|
-
if (lean && !include && meaningful)
|
|
201
|
+
if (lean && !include && meaningful) {
|
|
174
202
|
ctx.leanSkipped += 1;
|
|
203
|
+
}
|
|
175
204
|
if (include) {
|
|
176
205
|
ctx.nodes += 1;
|
|
177
206
|
ctx.lines.push('generic' === role && text.length > 0 && 0 === layout.length
|
|
178
207
|
? formatTextLine(depth, text)
|
|
179
|
-
: formatLine(child, depth, role, name, layout, text));
|
|
208
|
+
: formatLine(child, depth, role, name, layout, text, true === ctx.ids));
|
|
180
209
|
walk(child, depth + 1, ctx, announce);
|
|
181
210
|
}
|
|
182
211
|
else {
|
|
@@ -199,31 +228,36 @@ function walk(parent, depth, ctx, inLive = false) {
|
|
|
199
228
|
return;
|
|
200
229
|
}
|
|
201
230
|
const child = children[index];
|
|
202
|
-
if (child !== undefined)
|
|
231
|
+
if (child !== undefined) {
|
|
203
232
|
visit(child, depth, ctx, inLive);
|
|
233
|
+
}
|
|
204
234
|
}
|
|
205
235
|
}
|
|
206
236
|
function collectDialogs(root) {
|
|
207
237
|
const nodes = root.querySelectorAll('[role="dialog"], dialog[open], [aria-modal="true"]');
|
|
208
238
|
const names = [];
|
|
209
239
|
for (const node of nodes) {
|
|
210
|
-
if (isReticleOverlay(node))
|
|
240
|
+
if (isReticleOverlay(node)) {
|
|
211
241
|
continue;
|
|
212
|
-
|
|
242
|
+
}
|
|
243
|
+
if (isVisible(node)) {
|
|
213
244
|
names.push(getAccessibleName(node) || '(unnamed dialog)');
|
|
245
|
+
}
|
|
214
246
|
}
|
|
215
247
|
return names;
|
|
216
248
|
}
|
|
217
249
|
const MODAL_SELECTOR = '[role="dialog"], [role="alertdialog"], dialog[open], [aria-modal="true"]';
|
|
218
250
|
function overlayHidingPage(root) {
|
|
219
|
-
if (root !== document.body)
|
|
251
|
+
if (root !== document.body) {
|
|
220
252
|
return undefined;
|
|
253
|
+
}
|
|
221
254
|
const dialogs = document.body.querySelectorAll('[role="dialog"], dialog[open], [aria-modal="true"]');
|
|
222
255
|
let modal;
|
|
223
256
|
let hiddenCandidate;
|
|
224
257
|
for (const d of dialogs) {
|
|
225
|
-
if (isReticleOverlay(d))
|
|
258
|
+
if (isReticleOverlay(d)) {
|
|
226
259
|
continue;
|
|
260
|
+
}
|
|
227
261
|
if (isVisible(d)) {
|
|
228
262
|
modal = d;
|
|
229
263
|
break;
|
|
@@ -231,14 +265,17 @@ function overlayHidingPage(root) {
|
|
|
231
265
|
hiddenCandidate ??= d;
|
|
232
266
|
}
|
|
233
267
|
modal ??= hiddenCandidate;
|
|
234
|
-
if (modal === undefined)
|
|
268
|
+
if (modal === undefined) {
|
|
235
269
|
return undefined;
|
|
270
|
+
}
|
|
236
271
|
const modalEl = modal;
|
|
237
272
|
const outside = Array.from(document.body.children).filter((c) => !c.contains(modalEl));
|
|
238
|
-
if (0 === outside.length)
|
|
273
|
+
if (0 === outside.length) {
|
|
239
274
|
return undefined;
|
|
240
|
-
|
|
275
|
+
}
|
|
276
|
+
if (!outside.every((c) => 'true' === c.getAttribute('aria-hidden'))) {
|
|
241
277
|
return undefined;
|
|
278
|
+
}
|
|
242
279
|
return ('the rest of the page is aria-hidden behind an open overlay (a focus-trap modal), so this ' +
|
|
243
280
|
'snapshot shows only the overlay. If it will not close its cleanup may be stuck on a throttled ' +
|
|
244
281
|
'tab: dismiss the overlay, or snapshot with { scope } inside it.');
|
|
@@ -247,7 +284,7 @@ function buildStatus(root) {
|
|
|
247
284
|
const visibleDialogs = collectDialogs(root);
|
|
248
285
|
const overlay = overlayHidingPage(root);
|
|
249
286
|
return {
|
|
250
|
-
route:
|
|
287
|
+
route: pageRoute(),
|
|
251
288
|
title: document.title,
|
|
252
289
|
...(visibleDialogs.length > 0 ? { visibleDialogs } : {}),
|
|
253
290
|
...(overlay !== undefined ? { overlayHidingPage: overlay } : {}),
|
|
@@ -255,11 +292,20 @@ function buildStatus(root) {
|
|
|
255
292
|
}
|
|
256
293
|
export function buildSnapshot(options = {}) {
|
|
257
294
|
const mode = options.mode ?? SnapshotMode.FULL;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
295
|
+
const scope = options.scope?.trim();
|
|
296
|
+
let scopeEl = document.body;
|
|
297
|
+
if (undefined !== scope && '' !== scope) {
|
|
298
|
+
scopeEl = refs.resolve(scope);
|
|
299
|
+
if (null === scopeEl) {
|
|
300
|
+
try {
|
|
301
|
+
scopeEl = document.querySelector(scope);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const scopeMissing = null === scopeEl;
|
|
308
|
+
const root = scopeEl ?? document.body;
|
|
263
309
|
const status = buildStatus(root);
|
|
264
310
|
if (scopeMissing) {
|
|
265
311
|
return { tree: '', status, nodes: 0, truncated: false, scopeMissing: true };
|
|
@@ -278,16 +324,21 @@ export function buildSnapshot(options = {}) {
|
|
|
278
324
|
maxDepth: options.maxDepth ?? 20,
|
|
279
325
|
unread: [],
|
|
280
326
|
unreadOverflow: false,
|
|
327
|
+
...(true === options.ids ? { ids: true } : {}),
|
|
281
328
|
};
|
|
282
|
-
if (true === options.includeRoot)
|
|
329
|
+
if (true === options.includeRoot) {
|
|
283
330
|
visit(root, 0, ctx, false);
|
|
284
|
-
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
285
333
|
walk(root, 0, ctx);
|
|
334
|
+
}
|
|
286
335
|
if (0 === ctx.nodes && root === document.body) {
|
|
287
336
|
ctx.throughOverlay = true;
|
|
288
|
-
for (const child of Array.from(root.children))
|
|
289
|
-
if (overlayStamped(child) && null !== child.querySelector(MODAL_SELECTOR))
|
|
337
|
+
for (const child of Array.from(root.children)) {
|
|
338
|
+
if (overlayStamped(child) && null !== child.querySelector(MODAL_SELECTOR)) {
|
|
290
339
|
visit(child, 0, ctx, false);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
291
342
|
ctx.throughOverlay = false;
|
|
292
343
|
}
|
|
293
344
|
return {
|
|
@@ -300,5 +351,6 @@ export function buildSnapshot(options = {}) {
|
|
|
300
351
|
...(ctx.hiddenSkipped > 0 ? { hiddenSkipped: ctx.hiddenSkipped } : {}),
|
|
301
352
|
...(ctx.unread.length > 0 ? { unread: ctx.unread } : {}),
|
|
302
353
|
...(ctx.unreadOverflow ? { unreadOverflow: true } : {}),
|
|
354
|
+
...(true === options.audit ? { audit: auditLayout(root) } : {}),
|
|
303
355
|
};
|
|
304
356
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stableSelectorFor(el: HTMLElement): string | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getTestIdAttr, readTestId } from './addressing/testid-attr.js';
|
|
2
|
+
export function stableSelectorFor(el) {
|
|
3
|
+
const candidate = candidateSelectorFor(el);
|
|
4
|
+
if (undefined === candidate)
|
|
5
|
+
return undefined;
|
|
6
|
+
const matches = el.ownerDocument.querySelectorAll(candidate);
|
|
7
|
+
return 1 === matches.length && matches[0] === el ? candidate : undefined;
|
|
8
|
+
}
|
|
9
|
+
function candidateSelectorFor(el) {
|
|
10
|
+
const testid = readTestId(el);
|
|
11
|
+
if (null !== testid && 0 < testid.length) {
|
|
12
|
+
const escaped = testid.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
13
|
+
return `[${getTestIdAttr()}="${escaped}"]`;
|
|
14
|
+
}
|
|
15
|
+
const id = el.getAttribute('id');
|
|
16
|
+
if (null !== id && /^[A-Za-z_][A-Za-z0-9_-]*$/.test(id))
|
|
17
|
+
return `#${id}`;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ElementQuery } from '@reticlehq/core';
|
|
2
|
+
export declare function normaliseVisibleText(value: string): string;
|
|
3
|
+
export declare function foldTypography(value: string): string;
|
|
4
|
+
export declare function comparableText(value: string): string;
|
|
5
|
+
export declare function fuzzyVisibleText(actual: string, expected: string): boolean;
|
|
6
|
+
export declare function textMatches(actual: string, query: ElementQuery, expected: string): boolean;
|
|
7
|
+
export declare function exactVisibleText(actual: string, expected: string): boolean;
|
|
8
|
+
export declare function paintedCase(el: Element, text: string): string | undefined;
|
|
9
|
+
export declare const UNREADABLE_CONTRAST = 1.5;
|
|
10
|
+
export declare const UNREADABLE_OPACITY = 0.05;
|
|
11
|
+
export declare function unreadableText(el: Element): string | undefined;
|
|
12
|
+
export declare function unreadableMatch(matches: readonly Element[]): string | undefined;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { parentAcrossShadowBoundary } from './shadow-registry.js';
|
|
2
|
+
export function normaliseVisibleText(value) {
|
|
3
|
+
return value.replace(/\s+/g, ' ').trim().normalize('NFC');
|
|
4
|
+
}
|
|
5
|
+
const SINGLE_QUOTES = /[\u2018\u2019]/g;
|
|
6
|
+
const DOUBLE_QUOTES = /[\u201c\u201d]/g;
|
|
7
|
+
const DASHES = /[\u2013\u2014]/g;
|
|
8
|
+
export function foldTypography(value) {
|
|
9
|
+
return value.replace(SINGLE_QUOTES, "'").replace(DOUBLE_QUOTES, '"').replace(DASHES, '-');
|
|
10
|
+
}
|
|
11
|
+
export function comparableText(value) {
|
|
12
|
+
return foldTypography(normaliseVisibleText(value));
|
|
13
|
+
}
|
|
14
|
+
export function fuzzyVisibleText(actual, expected) {
|
|
15
|
+
return comparableText(actual).toLowerCase().includes(comparableText(expected).toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
export function textMatches(actual, query, expected) {
|
|
18
|
+
if (true !== query.matchCase) {
|
|
19
|
+
return fuzzyVisibleText(actual, expected);
|
|
20
|
+
}
|
|
21
|
+
return comparableText(actual).includes(comparableText(expected));
|
|
22
|
+
}
|
|
23
|
+
export function exactVisibleText(actual, expected) {
|
|
24
|
+
return comparableText(actual) === comparableText(expected);
|
|
25
|
+
}
|
|
26
|
+
const CASE_TRANSFORMS = {
|
|
27
|
+
uppercase: (text) => text.toUpperCase(),
|
|
28
|
+
lowercase: (text) => text.toLowerCase(),
|
|
29
|
+
capitalize: (text) => text.replace(/(^|\s)(\p{L})/gu, (_, gap, c) => gap + c.toUpperCase()),
|
|
30
|
+
};
|
|
31
|
+
export function paintedCase(el, text) {
|
|
32
|
+
const transform = el.ownerDocument.defaultView?.getComputedStyle(el).textTransform ?? '';
|
|
33
|
+
return CASE_TRANSFORMS[transform]?.(text);
|
|
34
|
+
}
|
|
35
|
+
export const UNREADABLE_CONTRAST = 1.5;
|
|
36
|
+
export const UNREADABLE_OPACITY = 0.05;
|
|
37
|
+
const CANVAS = [255, 255, 255, 1];
|
|
38
|
+
function parseRgb(value) {
|
|
39
|
+
const m = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+))?\s*\)$/.exec(value);
|
|
40
|
+
if (null === m) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] === undefined ? 1 : Number(m[4])];
|
|
44
|
+
}
|
|
45
|
+
function over(top, under) {
|
|
46
|
+
const a = top[3];
|
|
47
|
+
const mix = (t, u) => t * a + u * (1 - a);
|
|
48
|
+
return [mix(top[0], under[0]), mix(top[1], under[1]), mix(top[2], under[2]), 1];
|
|
49
|
+
}
|
|
50
|
+
function linear(v) {
|
|
51
|
+
const s = v / 255;
|
|
52
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
53
|
+
}
|
|
54
|
+
function luminance(c) {
|
|
55
|
+
return 0.2126 * linear(c[0]) + 0.7152 * linear(c[1]) + 0.0722 * linear(c[2]);
|
|
56
|
+
}
|
|
57
|
+
function contrast(a, b) {
|
|
58
|
+
const la = luminance(a);
|
|
59
|
+
const lb = luminance(b);
|
|
60
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
61
|
+
}
|
|
62
|
+
function show(c) {
|
|
63
|
+
return `rgb(${[c[0], c[1], c[2]].map((v) => String(Math.round(v))).join(', ')})`;
|
|
64
|
+
}
|
|
65
|
+
function outlined(style) {
|
|
66
|
+
const stroke = Number.parseFloat(style.getPropertyValue('-webkit-text-stroke-width'));
|
|
67
|
+
return /\dpx/.test(style.textShadow) || stroke > 0;
|
|
68
|
+
}
|
|
69
|
+
export function unreadableText(el) {
|
|
70
|
+
const view = el.ownerDocument.defaultView;
|
|
71
|
+
if (null === view) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const own = view.getComputedStyle(el);
|
|
75
|
+
const ink = parseRgb(own.color);
|
|
76
|
+
if (ink === undefined || outlined(own)) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
let opacity = 1;
|
|
80
|
+
const layers = [];
|
|
81
|
+
let opaque = false;
|
|
82
|
+
for (let node = el; null !== node; node = parentAcrossShadowBoundary(node)) {
|
|
83
|
+
const style = view.getComputedStyle(node);
|
|
84
|
+
const alpha = Number.parseFloat(style.opacity);
|
|
85
|
+
opacity *= Number.isNaN(alpha) ? 1 : alpha;
|
|
86
|
+
if (opaque) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if ('' !== style.backgroundImage && 'none' !== style.backgroundImage) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const fill = parseRgb(style.backgroundColor);
|
|
93
|
+
if (fill === undefined) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
if (fill[3] > 0) {
|
|
97
|
+
layers.push(fill);
|
|
98
|
+
}
|
|
99
|
+
opaque = fill[3] >= 1;
|
|
100
|
+
}
|
|
101
|
+
if (opacity < UNREADABLE_OPACITY) {
|
|
102
|
+
return `the composed opacity is ${opacity.toFixed(2)}`;
|
|
103
|
+
}
|
|
104
|
+
const back = layers.reduceRight((under, top) => over(top, under), CANVAS);
|
|
105
|
+
const painted = over(ink, back);
|
|
106
|
+
const ratio = contrast(painted, back);
|
|
107
|
+
if (ratio >= UNREADABLE_CONTRAST) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return `text ${show(painted)} on ${show(back)}: contrast ${ratio.toFixed(2)}:1`;
|
|
111
|
+
}
|
|
112
|
+
export function unreadableMatch(matches) {
|
|
113
|
+
const reasons = matches.map(unreadableText);
|
|
114
|
+
return reasons.length > 0 && reasons.every((r) => r !== undefined) ? reasons[0] : undefined;
|
|
115
|
+
}
|
package/dist/dom/theme.d.ts
CHANGED
package/dist/dom/theme.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
const THEME_ATTRIBUTE = 'data-theme';
|
|
2
|
-
|
|
2
|
+
const MAX_TOKEN_RULE_DEPTH = 8;
|
|
3
|
+
function isGroupingRule(rule) {
|
|
4
|
+
return 'cssRules' in rule && rule.cssRules instanceof CSSRuleList;
|
|
5
|
+
}
|
|
6
|
+
function collectTokenNames(rules, names, depth) {
|
|
7
|
+
if (depth <= 0)
|
|
8
|
+
return;
|
|
9
|
+
for (const rule of Array.from(rules)) {
|
|
10
|
+
if (rule instanceof CSSStyleRule) {
|
|
11
|
+
for (const prop of Array.from(rule.style)) {
|
|
12
|
+
if (prop.startsWith('--'))
|
|
13
|
+
names.add(prop);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else if (isGroupingRule(rule)) {
|
|
17
|
+
collectTokenNames(rule.cssRules, names, depth - 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function tokenNames() {
|
|
3
22
|
const names = new Set();
|
|
4
23
|
for (const sheet of Array.from(document.styleSheets)) {
|
|
5
24
|
let rules = null;
|
|
@@ -11,14 +30,7 @@ function tokenNames() {
|
|
|
11
30
|
}
|
|
12
31
|
if (null === rules)
|
|
13
32
|
continue;
|
|
14
|
-
|
|
15
|
-
if (!(rule instanceof CSSStyleRule))
|
|
16
|
-
continue;
|
|
17
|
-
for (const prop of Array.from(rule.style)) {
|
|
18
|
-
if (prop.startsWith('--'))
|
|
19
|
-
names.add(prop);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
33
|
+
collectTokenNames(rules, names, MAX_TOKEN_RULE_DEPTH);
|
|
22
34
|
}
|
|
23
35
|
return names;
|
|
24
36
|
}
|
package/dist/dom/virtualized.js
CHANGED
|
@@ -25,8 +25,12 @@ export function unmountedRowsIn(container) {
|
|
|
25
25
|
let top = Infinity;
|
|
26
26
|
let bottom = 0;
|
|
27
27
|
let totalHeight = 0;
|
|
28
|
+
const bounds = container.getBoundingClientRect();
|
|
29
|
+
const scaleY = container.offsetHeight > 0 && bounds.height > 0 ? bounds.height / container.offsetHeight : 1;
|
|
28
30
|
for (const row of rows) {
|
|
29
|
-
const rowTop = row.
|
|
31
|
+
const rowTop = (row.getBoundingClientRect().top - bounds.top) / scaleY -
|
|
32
|
+
container.clientTop +
|
|
33
|
+
container.scrollTop;
|
|
30
34
|
const rowBottom = rowTop + row.offsetHeight;
|
|
31
35
|
if (rowTop < top)
|
|
32
36
|
top = rowTop;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const HTML_DETAILS_TAG = "details";
|
|
2
|
+
export declare const HTML_DETAILS_OPEN_ATTRIBUTE = "open";
|
|
3
|
+
export declare const HTML_SUMMARY_TAG = "summary";
|
|
4
|
+
export declare function isHiddenByAriaOnly(el: Element): boolean;
|
|
5
|
+
export declare function isRenderedOutsideAriaHidden(el: Element): boolean;
|
|
6
|
+
export declare function isInViewport(el: Element, memo?: Map<Element, boolean>): boolean;
|
|
7
|
+
export declare function isVisible(el: Element, memo?: Map<Element, boolean>): boolean;
|