accented 0.0.0-20250303013509 → 0.0.0-20250424114613
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/NOTICE +14 -0
- package/README.md +10 -4
- package/dist/accented.d.ts +2 -2
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +10 -5
- package/dist/accented.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/dom-updater.d.ts.map +1 -1
- package/dist/dom-updater.js +66 -25
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +11 -7
- package/dist/elements/accented-dialog.d.ts.map +1 -1
- package/dist/elements/accented-dialog.js +85 -86
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +9 -5
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +35 -11
- package/dist/elements/accented-trigger.js.map +1 -1
- package/dist/fullscreen-listener.d.ts +2 -0
- package/dist/fullscreen-listener.d.ts.map +1 -0
- package/dist/fullscreen-listener.js +18 -0
- package/dist/fullscreen-listener.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -1
- package/dist/logger.js.map +1 -1
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +33 -19
- package/dist/scanner.js.map +1 -1
- package/dist/state.d.ts +2 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +3 -0
- package/dist/state.js.map +1 -1
- package/dist/task-queue.d.ts +2 -2
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +2 -1
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +42 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/are-elements-with-issues-equal.d.ts +3 -0
- package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -0
- package/dist/utils/are-elements-with-issues-equal.js +5 -0
- package/dist/utils/are-elements-with-issues-equal.js.map +1 -0
- package/dist/utils/containing-blocks.d.ts +3 -0
- package/dist/utils/containing-blocks.d.ts.map +1 -0
- package/dist/utils/containing-blocks.js +46 -0
- package/dist/utils/containing-blocks.js.map +1 -0
- package/dist/utils/contains.d.ts +2 -0
- package/dist/utils/contains.d.ts.map +1 -0
- package/dist/utils/contains.js +19 -0
- package/dist/utils/contains.js.map +1 -0
- package/dist/utils/deduplicate-nodes.d.ts +2 -0
- package/dist/utils/deduplicate-nodes.d.ts.map +1 -0
- package/dist/utils/deduplicate-nodes.js +5 -0
- package/dist/utils/deduplicate-nodes.js.map +1 -0
- package/dist/utils/dom-helpers.d.ts +9 -0
- package/dist/utils/dom-helpers.d.ts.map +1 -0
- package/dist/utils/dom-helpers.js +32 -0
- package/dist/utils/dom-helpers.js.map +1 -0
- package/dist/utils/ensure-non-empty.d.ts +2 -0
- package/dist/utils/ensure-non-empty.d.ts.map +1 -0
- package/dist/utils/ensure-non-empty.js +7 -0
- package/dist/utils/ensure-non-empty.js.map +1 -0
- package/dist/utils/get-element-position.d.ts +8 -0
- package/dist/utils/get-element-position.d.ts.map +1 -1
- package/dist/utils/get-element-position.js +27 -11
- package/dist/utils/get-element-position.js.map +1 -1
- package/dist/utils/get-parent.d.ts +2 -0
- package/dist/utils/get-parent.d.ts.map +1 -0
- package/dist/utils/get-parent.js +12 -0
- package/dist/utils/get-parent.js.map +1 -0
- package/dist/utils/get-scan-context.d.ts +3 -0
- package/dist/utils/get-scan-context.d.ts.map +1 -0
- package/dist/utils/get-scan-context.js +28 -0
- package/dist/utils/get-scan-context.js.map +1 -0
- package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/get-scrollable-ancestors.js +6 -2
- package/dist/utils/get-scrollable-ancestors.js.map +1 -1
- package/dist/utils/is-node-in-scan-context.d.ts +3 -0
- package/dist/utils/is-node-in-scan-context.d.ts.map +1 -0
- package/dist/utils/is-node-in-scan-context.js +26 -0
- package/dist/utils/is-node-in-scan-context.js.map +1 -0
- package/dist/utils/normalize-context.d.ts +3 -0
- package/dist/utils/normalize-context.d.ts.map +1 -0
- package/dist/utils/normalize-context.js +57 -0
- package/dist/utils/normalize-context.js.map +1 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +10 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js +64 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -0
- package/dist/utils/transform-violations.d.ts +1 -1
- package/dist/utils/transform-violations.d.ts.map +1 -1
- package/dist/utils/transform-violations.js +18 -5
- package/dist/utils/transform-violations.js.map +1 -1
- package/dist/utils/update-elements-with-issues.d.ts +10 -4
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +33 -6
- package/dist/utils/update-elements-with-issues.js.map +1 -1
- package/dist/validate-options.d.ts.map +1 -1
- package/dist/validate-options.js +86 -0
- package/dist/validate-options.js.map +1 -1
- package/package.json +8 -3
- package/src/accented.ts +10 -5
- package/src/constants.ts +1 -0
- package/src/dom-updater.ts +70 -24
- package/src/elements/accented-dialog.ts +88 -90
- package/src/elements/accented-trigger.ts +36 -12
- package/src/fullscreen-listener.ts +17 -0
- package/src/logger.ts +9 -1
- package/src/scanner.ts +37 -20
- package/src/state.ts +10 -2
- package/src/task-queue.ts +6 -4
- package/src/types.ts +55 -9
- package/src/utils/are-elements-with-issues-equal.ts +9 -0
- package/src/utils/containing-blocks.ts +57 -0
- package/src/utils/contains.test.ts +55 -0
- package/src/utils/contains.ts +19 -0
- package/src/utils/deduplicate-nodes.ts +3 -0
- package/src/utils/dom-helpers.ts +38 -0
- package/src/utils/ensure-non-empty.ts +6 -0
- package/src/utils/get-element-position.ts +28 -11
- package/src/utils/get-parent.ts +14 -0
- package/src/utils/get-scan-context.test.ts +79 -0
- package/src/utils/get-scan-context.ts +39 -0
- package/src/utils/get-scrollable-ancestors.ts +10 -5
- package/src/utils/is-node-in-scan-context.test.ts +70 -0
- package/src/utils/is-node-in-scan-context.ts +29 -0
- package/src/utils/normalize-context.test.ts +105 -0
- package/src/utils/normalize-context.ts +58 -0
- package/src/utils/shadow-dom-aware-mutation-observer.ts +78 -0
- package/src/utils/transform-violations.test.ts +10 -8
- package/src/utils/transform-violations.ts +20 -6
- package/src/utils/update-elements-with-issues.test.ts +102 -15
- package/src/utils/update-elements-with-issues.ts +51 -7
- package/src/validate-options.ts +88 -1
- package/dist/utils/is-html-element.d.ts +0 -2
- package/dist/utils/is-html-element.d.ts.map +0 -1
- package/dist/utils/is-html-element.js +0 -7
- package/dist/utils/is-html-element.js.map +0 -1
- package/src/utils/is-html-element.ts +0 -6
|
@@ -8,7 +8,7 @@ import updateElementsWithIssues from './update-elements-with-issues';
|
|
|
8
8
|
import type { AxeResults, ImpactValue } from 'axe-core';
|
|
9
9
|
import type { AccentedTrigger } from '../elements/accented-trigger';
|
|
10
10
|
type Violation = AxeResults['violations'][number];
|
|
11
|
-
type
|
|
11
|
+
type AxeNode = Violation['nodes'][number];
|
|
12
12
|
|
|
13
13
|
const win: Window & { CSS: typeof CSS } = {
|
|
14
14
|
document: {
|
|
@@ -18,12 +18,14 @@ const win: Window & { CSS: typeof CSS } = {
|
|
|
18
18
|
setProperty: () => {}
|
|
19
19
|
},
|
|
20
20
|
dataset: {}
|
|
21
|
-
})
|
|
21
|
+
}),
|
|
22
|
+
contains: () => true,
|
|
22
23
|
},
|
|
23
24
|
// @ts-expect-error we're missing a lot of properties
|
|
24
25
|
getComputedStyle: () => ({
|
|
25
26
|
zIndex: '',
|
|
26
|
-
direction: 'ltr'
|
|
27
|
+
direction: 'ltr',
|
|
28
|
+
getPropertyValue: () => 'none'
|
|
27
29
|
}),
|
|
28
30
|
// @ts-expect-error we're missing a lot of properties
|
|
29
31
|
CSS: {
|
|
@@ -33,12 +35,26 @@ const win: Window & { CSS: typeof CSS } = {
|
|
|
33
35
|
|
|
34
36
|
const getBoundingClientRect = () => ({});
|
|
35
37
|
|
|
38
|
+
const getRootNode = (): Node => ({} as Node);
|
|
39
|
+
|
|
40
|
+
const baseElement = {
|
|
41
|
+
getBoundingClientRect,
|
|
42
|
+
getRootNode,
|
|
43
|
+
style: {
|
|
44
|
+
getPropertyValue: () => ''
|
|
45
|
+
},
|
|
46
|
+
closest: () => null,
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
// @ts-expect-error element is not HTMLElement
|
|
37
|
-
const element1: HTMLElement = {
|
|
50
|
+
const element1: HTMLElement = {...baseElement, isConnected: true};
|
|
38
51
|
// @ts-expect-error element is not HTMLElement
|
|
39
|
-
const element2: HTMLElement = {
|
|
52
|
+
const element2: HTMLElement = {...baseElement, isConnected: true};
|
|
40
53
|
// @ts-expect-error element is not HTMLElement
|
|
41
|
-
const element3: HTMLElement = {
|
|
54
|
+
const element3: HTMLElement = {...baseElement, isConnected: false};
|
|
55
|
+
|
|
56
|
+
// @ts-expect-error rootNode is not Node
|
|
57
|
+
const rootNode: Node = {};
|
|
42
58
|
|
|
43
59
|
const trigger = win.document.createElement('accented-trigger') as AccentedTrigger;
|
|
44
60
|
|
|
@@ -61,17 +77,17 @@ const commonNodeProps = {
|
|
|
61
77
|
target: ['div']
|
|
62
78
|
};
|
|
63
79
|
|
|
64
|
-
const node1:
|
|
80
|
+
const node1: AxeNode = {
|
|
65
81
|
...commonNodeProps,
|
|
66
82
|
element: element1,
|
|
67
83
|
};
|
|
68
84
|
|
|
69
|
-
const node2:
|
|
85
|
+
const node2: AxeNode = {
|
|
70
86
|
...commonNodeProps,
|
|
71
87
|
element: element2,
|
|
72
88
|
};
|
|
73
89
|
|
|
74
|
-
const node3:
|
|
90
|
+
const node3: AxeNode = {
|
|
75
91
|
...commonNodeProps,
|
|
76
92
|
element: element3,
|
|
77
93
|
};
|
|
@@ -130,29 +146,46 @@ const issue3: Issue = {
|
|
|
130
146
|
...commonIssueProps
|
|
131
147
|
};
|
|
132
148
|
|
|
149
|
+
const scanContext = {
|
|
150
|
+
include: [win.document],
|
|
151
|
+
exclude: []
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
suite('updateElementsWithIssues', () => {
|
|
134
155
|
test('no changes', () => {
|
|
135
156
|
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
136
157
|
{
|
|
137
158
|
id: 1,
|
|
138
159
|
element: element1,
|
|
160
|
+
rootNode,
|
|
161
|
+
skipRender: false,
|
|
139
162
|
position,
|
|
140
163
|
visible,
|
|
141
164
|
trigger,
|
|
165
|
+
anchorNameValue: 'none',
|
|
142
166
|
scrollableAncestors,
|
|
143
167
|
issues: signal([issue1])
|
|
144
168
|
},
|
|
145
169
|
{
|
|
146
170
|
id: 2,
|
|
147
171
|
element: element2,
|
|
172
|
+
rootNode,
|
|
173
|
+
skipRender: false,
|
|
148
174
|
position,
|
|
149
175
|
visible,
|
|
150
176
|
trigger,
|
|
177
|
+
anchorNameValue: 'none',
|
|
151
178
|
scrollableAncestors,
|
|
152
179
|
issues: signal([issue2])
|
|
153
180
|
}
|
|
154
181
|
]);
|
|
155
|
-
updateElementsWithIssues(
|
|
182
|
+
updateElementsWithIssues({
|
|
183
|
+
extendedElementsWithIssues,
|
|
184
|
+
scanContext,
|
|
185
|
+
violations: [violation1, violation2],
|
|
186
|
+
win,
|
|
187
|
+
name: 'accented'
|
|
188
|
+
});
|
|
156
189
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
157
190
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
158
191
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -165,23 +198,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
165
198
|
{
|
|
166
199
|
id: 1,
|
|
167
200
|
element: element1,
|
|
201
|
+
rootNode,
|
|
202
|
+
skipRender: false,
|
|
168
203
|
position,
|
|
169
204
|
visible,
|
|
170
205
|
trigger,
|
|
206
|
+
anchorNameValue: 'none',
|
|
171
207
|
scrollableAncestors,
|
|
172
208
|
issues: signal([issue1])
|
|
173
209
|
},
|
|
174
210
|
{
|
|
175
211
|
id: 2,
|
|
176
212
|
element: element2,
|
|
213
|
+
rootNode,
|
|
214
|
+
skipRender: false,
|
|
177
215
|
position,
|
|
178
216
|
visible,
|
|
179
217
|
trigger,
|
|
218
|
+
anchorNameValue: 'none',
|
|
180
219
|
scrollableAncestors,
|
|
181
220
|
issues: signal([issue2])
|
|
182
221
|
}
|
|
183
222
|
]);
|
|
184
|
-
updateElementsWithIssues(
|
|
223
|
+
updateElementsWithIssues({
|
|
224
|
+
extendedElementsWithIssues,
|
|
225
|
+
scanContext,
|
|
226
|
+
violations: [violation1, violation2, violation3],
|
|
227
|
+
win,
|
|
228
|
+
name: 'accented'
|
|
229
|
+
});
|
|
185
230
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
186
231
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
187
232
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -194,23 +239,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
194
239
|
{
|
|
195
240
|
id: 1,
|
|
196
241
|
element: element1,
|
|
242
|
+
rootNode,
|
|
243
|
+
skipRender: false,
|
|
197
244
|
position,
|
|
198
245
|
visible,
|
|
199
246
|
trigger,
|
|
247
|
+
anchorNameValue: 'none',
|
|
200
248
|
scrollableAncestors,
|
|
201
249
|
issues: signal([issue1])
|
|
202
250
|
},
|
|
203
251
|
{
|
|
204
252
|
id: 2,
|
|
205
253
|
element: element2,
|
|
254
|
+
rootNode,
|
|
255
|
+
skipRender: false,
|
|
206
256
|
position,
|
|
207
257
|
visible,
|
|
208
258
|
trigger,
|
|
259
|
+
anchorNameValue: 'none',
|
|
209
260
|
scrollableAncestors,
|
|
210
261
|
issues: signal([issue2, issue3])
|
|
211
262
|
}
|
|
212
263
|
]);
|
|
213
|
-
updateElementsWithIssues(
|
|
264
|
+
updateElementsWithIssues({
|
|
265
|
+
extendedElementsWithIssues,
|
|
266
|
+
scanContext,
|
|
267
|
+
violations: [violation1, violation2],
|
|
268
|
+
win,
|
|
269
|
+
name: 'accented'
|
|
270
|
+
});
|
|
214
271
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
215
272
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
216
273
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -223,14 +280,23 @@ suite('updateElementsWithIssues', () => {
|
|
|
223
280
|
{
|
|
224
281
|
id: 1,
|
|
225
282
|
element: element1,
|
|
283
|
+
rootNode,
|
|
284
|
+
skipRender: false,
|
|
226
285
|
position,
|
|
227
286
|
visible,
|
|
228
287
|
trigger,
|
|
288
|
+
anchorNameValue: 'none',
|
|
229
289
|
scrollableAncestors,
|
|
230
290
|
issues: signal([issue1])
|
|
231
291
|
}
|
|
232
292
|
]);
|
|
233
|
-
updateElementsWithIssues(
|
|
293
|
+
updateElementsWithIssues({
|
|
294
|
+
extendedElementsWithIssues,
|
|
295
|
+
scanContext,
|
|
296
|
+
violations: [violation1, violation2],
|
|
297
|
+
win,
|
|
298
|
+
name: 'accented'
|
|
299
|
+
});
|
|
234
300
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
235
301
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
236
302
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -243,14 +309,23 @@ suite('updateElementsWithIssues', () => {
|
|
|
243
309
|
{
|
|
244
310
|
id: 1,
|
|
245
311
|
element: element1,
|
|
312
|
+
rootNode,
|
|
313
|
+
skipRender: false,
|
|
246
314
|
position,
|
|
247
315
|
visible,
|
|
248
316
|
trigger,
|
|
317
|
+
anchorNameValue: 'none',
|
|
249
318
|
scrollableAncestors,
|
|
250
319
|
issues: signal([issue1])
|
|
251
320
|
}
|
|
252
321
|
]);
|
|
253
|
-
updateElementsWithIssues(
|
|
322
|
+
updateElementsWithIssues({
|
|
323
|
+
extendedElementsWithIssues,
|
|
324
|
+
scanContext,
|
|
325
|
+
violations: [violation1, violation4],
|
|
326
|
+
win,
|
|
327
|
+
name: 'accented'
|
|
328
|
+
});
|
|
254
329
|
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
255
330
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
256
331
|
});
|
|
@@ -260,23 +335,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
260
335
|
{
|
|
261
336
|
id: 1,
|
|
262
337
|
element: element1,
|
|
338
|
+
rootNode,
|
|
339
|
+
skipRender: false,
|
|
263
340
|
position,
|
|
264
341
|
visible,
|
|
265
342
|
trigger,
|
|
343
|
+
anchorNameValue: 'none',
|
|
266
344
|
scrollableAncestors,
|
|
267
345
|
issues: signal([issue1])
|
|
268
346
|
},
|
|
269
347
|
{
|
|
270
348
|
id: 2,
|
|
271
349
|
element: element2,
|
|
350
|
+
rootNode,
|
|
351
|
+
skipRender: false,
|
|
272
352
|
position,
|
|
273
353
|
visible,
|
|
274
354
|
trigger,
|
|
355
|
+
anchorNameValue: 'none',
|
|
275
356
|
scrollableAncestors,
|
|
276
357
|
issues: signal([issue2])
|
|
277
358
|
}
|
|
278
359
|
]);
|
|
279
|
-
updateElementsWithIssues(
|
|
360
|
+
updateElementsWithIssues({
|
|
361
|
+
extendedElementsWithIssues,
|
|
362
|
+
scanContext,
|
|
363
|
+
violations: [violation1],
|
|
364
|
+
win,
|
|
365
|
+
name: 'accented'
|
|
366
|
+
});
|
|
280
367
|
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
281
368
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
282
369
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -1,40 +1,79 @@
|
|
|
1
1
|
import type { AxeResults } from 'axe-core';
|
|
2
2
|
import type { Signal } from '@preact/signals-core';
|
|
3
3
|
import { batch, signal } from '@preact/signals-core';
|
|
4
|
-
import type { ExtendedElementWithIssues } from '../types';
|
|
4
|
+
import type { ExtendedElementWithIssues, ScanContext } from '../types';
|
|
5
5
|
import transformViolations from './transform-violations.js';
|
|
6
|
+
import areElementsWithIssuesEqual from './are-elements-with-issues-equal.js';
|
|
6
7
|
import areIssueSetsEqual from './are-issue-sets-equal.js';
|
|
8
|
+
import isNodeInScanContext from './is-node-in-scan-context.js';
|
|
7
9
|
import type { AccentedTrigger } from '../elements/accented-trigger';
|
|
8
10
|
import type { AccentedDialog } from '../elements/accented-dialog';
|
|
9
11
|
import getElementPosition from './get-element-position.js';
|
|
10
12
|
import getScrollableAncestors from './get-scrollable-ancestors.js';
|
|
11
13
|
import supportsAnchorPositioning from './supports-anchor-positioning.js';
|
|
14
|
+
import { isSvgElement } from './dom-helpers.js';
|
|
15
|
+
import getParent from './get-parent.js';
|
|
16
|
+
|
|
17
|
+
function shouldSkipRender(element: Element): boolean {
|
|
18
|
+
|
|
19
|
+
// Skip rendering if the element is inside an SVG:
|
|
20
|
+
// https://github.com/pomerantsev/accented/issues/62
|
|
21
|
+
const parent = getParent(element);
|
|
22
|
+
const isInsideSvg = Boolean(parent && isSvgElement(parent));
|
|
23
|
+
|
|
24
|
+
// Some issues, such as meta-viewport, are on <head> descendants,
|
|
25
|
+
// but since <head> is never rendered, we don't want to output anything
|
|
26
|
+
// for those in the DOM.
|
|
27
|
+
// We're not anticipating the use of shadow DOM in <head>,
|
|
28
|
+
// so the use of .closest() should be fine.
|
|
29
|
+
const isInsideHead = element.closest('head') !== null;
|
|
30
|
+
|
|
31
|
+
return isInsideSvg || isInsideHead;
|
|
32
|
+
}
|
|
12
33
|
|
|
13
34
|
let count = 0;
|
|
14
35
|
|
|
15
|
-
export default function updateElementsWithIssues(
|
|
16
|
-
|
|
36
|
+
export default function updateElementsWithIssues({
|
|
37
|
+
extendedElementsWithIssues,
|
|
38
|
+
scanContext,
|
|
39
|
+
violations,
|
|
40
|
+
win,
|
|
41
|
+
name
|
|
42
|
+
}: {
|
|
43
|
+
extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>>,
|
|
44
|
+
scanContext: ScanContext,
|
|
45
|
+
violations: typeof AxeResults.violations,
|
|
46
|
+
win: Window & { CSS: typeof CSS },
|
|
47
|
+
name: string
|
|
48
|
+
}) {
|
|
49
|
+
const updatedElementsWithIssues = transformViolations(violations, name);
|
|
17
50
|
|
|
18
51
|
batch(() => {
|
|
19
52
|
for (const updatedElementWithIssues of updatedElementsWithIssues) {
|
|
20
|
-
const existingElementIndex = extendedElementsWithIssues.value.findIndex(extendedElementWithIssues => extendedElementWithIssues
|
|
53
|
+
const existingElementIndex = extendedElementsWithIssues.value.findIndex(extendedElementWithIssues => areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues));
|
|
21
54
|
if (existingElementIndex > -1 && extendedElementsWithIssues.value[existingElementIndex] && !areIssueSetsEqual(extendedElementsWithIssues.value[existingElementIndex].issues.value, updatedElementWithIssues.issues)) {
|
|
22
55
|
extendedElementsWithIssues.value[existingElementIndex].issues.value = updatedElementWithIssues.issues;
|
|
23
56
|
}
|
|
24
57
|
}
|
|
25
58
|
|
|
26
59
|
const addedElementsWithIssues = updatedElementsWithIssues.filter(updatedElementWithIssues => {
|
|
27
|
-
return !extendedElementsWithIssues.value.some(extendedElementWithIssues => extendedElementWithIssues
|
|
60
|
+
return !extendedElementsWithIssues.value.some(extendedElementWithIssues => areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues));
|
|
28
61
|
});
|
|
29
62
|
|
|
63
|
+
// Only consider an element to be removed in two cases:
|
|
64
|
+
// 1. It has been removed from the DOM.
|
|
65
|
+
// 2. It is within the scan context, but not among updatedElementsWithIssues.
|
|
30
66
|
const removedElementsWithIssues = extendedElementsWithIssues.value.filter(extendedElementWithIssues => {
|
|
31
|
-
|
|
67
|
+
const isConnected = extendedElementWithIssues.element.isConnected;
|
|
68
|
+
const hasNoMoreIssues = isNodeInScanContext(extendedElementWithIssues.element, scanContext)
|
|
69
|
+
&& !updatedElementsWithIssues.some(updatedElementWithIssues => areElementsWithIssuesEqual(updatedElementWithIssues, extendedElementWithIssues));
|
|
70
|
+
return !isConnected || hasNoMoreIssues;
|
|
32
71
|
});
|
|
33
72
|
|
|
34
73
|
if (addedElementsWithIssues.length > 0 || removedElementsWithIssues.length > 0) {
|
|
35
74
|
extendedElementsWithIssues.value = [...extendedElementsWithIssues.value]
|
|
36
75
|
.filter(extendedElementWithIssues => {
|
|
37
|
-
return !removedElementsWithIssues.some(removedElementWithIssues => removedElementWithIssues
|
|
76
|
+
return !removedElementsWithIssues.some(removedElementWithIssues => areElementsWithIssuesEqual(removedElementWithIssues, extendedElementWithIssues));
|
|
38
77
|
})
|
|
39
78
|
.concat(addedElementsWithIssues
|
|
40
79
|
.filter(addedElementWithIssues => addedElementWithIssues.element.isConnected)
|
|
@@ -62,9 +101,14 @@ export default function updateElementsWithIssues(extendedElementsWithIssues: Sig
|
|
|
62
101
|
return {
|
|
63
102
|
id,
|
|
64
103
|
element: addedElementWithIssues.element,
|
|
104
|
+
skipRender: shouldSkipRender(addedElementWithIssues.element),
|
|
105
|
+
rootNode: addedElementWithIssues.rootNode,
|
|
65
106
|
visible: trigger.visible,
|
|
66
107
|
position: trigger.position,
|
|
67
108
|
scrollableAncestors: signal(scrollableAncestors),
|
|
109
|
+
anchorNameValue:
|
|
110
|
+
addedElementWithIssues.element.style.getPropertyValue('anchor-name')
|
|
111
|
+
|| win.getComputedStyle(addedElementWithIssues.element).getPropertyValue('anchor-name'),
|
|
68
112
|
trigger,
|
|
69
113
|
issues
|
|
70
114
|
};
|
package/src/validate-options.ts
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
|
-
import type { AccentedOptions } from './types';
|
|
1
|
+
import type { Selector, SelectorList, ContextProp, ContextObject, AccentedOptions, Context } from './types';
|
|
2
2
|
import { allowedAxeOptions } from './types.js';
|
|
3
|
+
import { isNode, isNodeList } from './utils/dom-helpers.js';
|
|
4
|
+
|
|
5
|
+
function isSelector(contextFragment: Context): contextFragment is Selector {
|
|
6
|
+
return typeof contextFragment === 'string'
|
|
7
|
+
|| isNode(contextFragment)
|
|
8
|
+
|| 'fromShadowDom' in contextFragment;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function validateSelector(selector: Selector) {
|
|
12
|
+
if (typeof selector === 'string') {
|
|
13
|
+
return;
|
|
14
|
+
} else if (isNode(selector)) {
|
|
15
|
+
return;
|
|
16
|
+
} else if ('fromShadowDom' in selector) {
|
|
17
|
+
if (!Array.isArray(selector.fromShadowDom)
|
|
18
|
+
|| selector.fromShadowDom.length < 2 ||
|
|
19
|
+
!selector.fromShadowDom.every(item => typeof item === 'string')
|
|
20
|
+
) {
|
|
21
|
+
throw new TypeError(`Accented: invalid argument. \`fromShadowDom\` must be an array of strings with at least 2 elements. It’s currently set to ${selector.fromShadowDom}.`);
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
} else {
|
|
25
|
+
const neverSelector: never = selector;
|
|
26
|
+
throw new TypeError(`Accented: invalid argument. The selector must be one of: string, Node, or an object with a \`fromShadowDom\` property. It’s currently set to ${neverSelector}.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSelectorList(contextFragment: Context): contextFragment is SelectorList {
|
|
31
|
+
return (typeof contextFragment === 'object' && isNodeList(contextFragment))
|
|
32
|
+
|| (Array.isArray(contextFragment) && contextFragment.every(item => isSelector(item)));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateSelectorList(selectorList: SelectorList) {
|
|
36
|
+
if (isNodeList(selectorList)) {
|
|
37
|
+
return;
|
|
38
|
+
} else if (Array.isArray(selectorList)) {
|
|
39
|
+
for (const selector of selectorList) {
|
|
40
|
+
validateSelector(selector);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
const neverSelectorList: never = selectorList;
|
|
44
|
+
throw new TypeError(`Accented: invalid argument. The selector list must either be a NodeList or an array. It’s currently set to ${neverSelectorList}.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isContextProp(contextFragment: Context): contextFragment is ContextProp {
|
|
49
|
+
return isSelector(contextFragment) || isSelectorList(contextFragment);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validateContextProp(context: Selector | SelectorList) {
|
|
53
|
+
if (isSelector(context)) {
|
|
54
|
+
validateSelector(context);
|
|
55
|
+
} else if (isSelectorList(context)) {
|
|
56
|
+
validateSelectorList(context);
|
|
57
|
+
} else {
|
|
58
|
+
const neverContext: never = context;
|
|
59
|
+
throw new TypeError(`Accented: invalid argument. The context property must either be a selector or a selector list. It’s currently set to ${neverContext}.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isContextObject(contextFragment: Context): contextFragment is ContextObject {
|
|
64
|
+
return typeof contextFragment === 'object' && contextFragment !== null
|
|
65
|
+
&& ('include' in contextFragment || 'exclude' in contextFragment);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateContextObject(contextObject: ContextObject) {
|
|
69
|
+
if ('include' in contextObject) {
|
|
70
|
+
validateContextProp(contextObject.include!);
|
|
71
|
+
}
|
|
72
|
+
if ('exclude' in contextObject) {
|
|
73
|
+
validateContextProp(contextObject.exclude!);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateContext(context: Context) {
|
|
78
|
+
if (isContextProp(context)) {
|
|
79
|
+
validateContextProp(context);
|
|
80
|
+
} else if (isContextObject(context)) {
|
|
81
|
+
validateContextObject(context);
|
|
82
|
+
} else {
|
|
83
|
+
const neverContext: never = context;
|
|
84
|
+
throw new TypeError(`Accented: invalid context argument. It’s currently set to ${neverContext}.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
3
87
|
|
|
4
88
|
// The space of valid CSS and HTML names is wider than this,
|
|
5
89
|
// but with Unicode it gets complicated quickly, so I'm sticking to only allowing
|
|
@@ -41,4 +125,7 @@ export default function validateOptions(options: AccentedOptions) {
|
|
|
41
125
|
throw new TypeError(`Accented: invalid argument. \`axeOptions\` contains the following unsupported keys: ${unsupportedKeys.join(', ')}. Valid options are: ${allowedAxeOptions.join(', ')}.`);
|
|
42
126
|
}
|
|
43
127
|
}
|
|
128
|
+
if (options.context !== undefined) {
|
|
129
|
+
validateContext(options.context);
|
|
130
|
+
}
|
|
44
131
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"is-html-element.d.ts","sourceRoot":"","sources":["../../src/utils/is-html-element.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,WAAW,CAK9E"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export default function isHtmlElement(element) {
|
|
2
|
-
// We can't use instanceof because it may not work across contexts
|
|
3
|
-
// (such as when an element is moved from an iframe).
|
|
4
|
-
// This heuristic seems to be the most robust and fastest that I could think of.
|
|
5
|
-
return element.constructor.name.startsWith('HTML');
|
|
6
|
-
}
|
|
7
|
-
//# sourceMappingURL=is-html-element.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"is-html-element.js","sourceRoot":"","sources":["../../src/utils/is-html-element.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,OAAgB;IACpD,kEAAkE;IAClE,qDAAqD;IACrD,gFAAgF;IAChF,OAAO,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export default function isHtmlElement(element: Element): element is HTMLElement {
|
|
2
|
-
// We can't use instanceof because it may not work across contexts
|
|
3
|
-
// (such as when an element is moved from an iframe).
|
|
4
|
-
// This heuristic seems to be the most robust and fastest that I could think of.
|
|
5
|
-
return element.constructor.name.startsWith('HTML');
|
|
6
|
-
}
|