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.
Files changed (145) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +10 -4
  3. package/dist/accented.d.ts +2 -2
  4. package/dist/accented.d.ts.map +1 -1
  5. package/dist/accented.js +10 -5
  6. package/dist/accented.js.map +1 -1
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/constants.js +1 -0
  10. package/dist/constants.js.map +1 -1
  11. package/dist/dom-updater.d.ts.map +1 -1
  12. package/dist/dom-updater.js +66 -25
  13. package/dist/dom-updater.js.map +1 -1
  14. package/dist/elements/accented-dialog.d.ts +11 -7
  15. package/dist/elements/accented-dialog.d.ts.map +1 -1
  16. package/dist/elements/accented-dialog.js +85 -86
  17. package/dist/elements/accented-dialog.js.map +1 -1
  18. package/dist/elements/accented-trigger.d.ts +9 -5
  19. package/dist/elements/accented-trigger.d.ts.map +1 -1
  20. package/dist/elements/accented-trigger.js +35 -11
  21. package/dist/elements/accented-trigger.js.map +1 -1
  22. package/dist/fullscreen-listener.d.ts +2 -0
  23. package/dist/fullscreen-listener.d.ts.map +1 -0
  24. package/dist/fullscreen-listener.js +18 -0
  25. package/dist/fullscreen-listener.js.map +1 -0
  26. package/dist/logger.d.ts.map +1 -1
  27. package/dist/logger.js +4 -1
  28. package/dist/logger.js.map +1 -1
  29. package/dist/scanner.d.ts +2 -2
  30. package/dist/scanner.d.ts.map +1 -1
  31. package/dist/scanner.js +33 -19
  32. package/dist/scanner.js.map +1 -1
  33. package/dist/state.d.ts +2 -1
  34. package/dist/state.d.ts.map +1 -1
  35. package/dist/state.js +3 -0
  36. package/dist/state.js.map +1 -1
  37. package/dist/task-queue.d.ts +2 -2
  38. package/dist/task-queue.d.ts.map +1 -1
  39. package/dist/task-queue.js +2 -1
  40. package/dist/task-queue.js.map +1 -1
  41. package/dist/types.d.ts +42 -8
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/dist/utils/are-elements-with-issues-equal.d.ts +3 -0
  45. package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -0
  46. package/dist/utils/are-elements-with-issues-equal.js +5 -0
  47. package/dist/utils/are-elements-with-issues-equal.js.map +1 -0
  48. package/dist/utils/containing-blocks.d.ts +3 -0
  49. package/dist/utils/containing-blocks.d.ts.map +1 -0
  50. package/dist/utils/containing-blocks.js +46 -0
  51. package/dist/utils/containing-blocks.js.map +1 -0
  52. package/dist/utils/contains.d.ts +2 -0
  53. package/dist/utils/contains.d.ts.map +1 -0
  54. package/dist/utils/contains.js +19 -0
  55. package/dist/utils/contains.js.map +1 -0
  56. package/dist/utils/deduplicate-nodes.d.ts +2 -0
  57. package/dist/utils/deduplicate-nodes.d.ts.map +1 -0
  58. package/dist/utils/deduplicate-nodes.js +5 -0
  59. package/dist/utils/deduplicate-nodes.js.map +1 -0
  60. package/dist/utils/dom-helpers.d.ts +9 -0
  61. package/dist/utils/dom-helpers.d.ts.map +1 -0
  62. package/dist/utils/dom-helpers.js +32 -0
  63. package/dist/utils/dom-helpers.js.map +1 -0
  64. package/dist/utils/ensure-non-empty.d.ts +2 -0
  65. package/dist/utils/ensure-non-empty.d.ts.map +1 -0
  66. package/dist/utils/ensure-non-empty.js +7 -0
  67. package/dist/utils/ensure-non-empty.js.map +1 -0
  68. package/dist/utils/get-element-position.d.ts +8 -0
  69. package/dist/utils/get-element-position.d.ts.map +1 -1
  70. package/dist/utils/get-element-position.js +27 -11
  71. package/dist/utils/get-element-position.js.map +1 -1
  72. package/dist/utils/get-parent.d.ts +2 -0
  73. package/dist/utils/get-parent.d.ts.map +1 -0
  74. package/dist/utils/get-parent.js +12 -0
  75. package/dist/utils/get-parent.js.map +1 -0
  76. package/dist/utils/get-scan-context.d.ts +3 -0
  77. package/dist/utils/get-scan-context.d.ts.map +1 -0
  78. package/dist/utils/get-scan-context.js +28 -0
  79. package/dist/utils/get-scan-context.js.map +1 -0
  80. package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
  81. package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
  82. package/dist/utils/get-scrollable-ancestors.js +6 -2
  83. package/dist/utils/get-scrollable-ancestors.js.map +1 -1
  84. package/dist/utils/is-node-in-scan-context.d.ts +3 -0
  85. package/dist/utils/is-node-in-scan-context.d.ts.map +1 -0
  86. package/dist/utils/is-node-in-scan-context.js +26 -0
  87. package/dist/utils/is-node-in-scan-context.js.map +1 -0
  88. package/dist/utils/normalize-context.d.ts +3 -0
  89. package/dist/utils/normalize-context.d.ts.map +1 -0
  90. package/dist/utils/normalize-context.js +57 -0
  91. package/dist/utils/normalize-context.js.map +1 -0
  92. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +10 -0
  93. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -0
  94. package/dist/utils/shadow-dom-aware-mutation-observer.js +64 -0
  95. package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -0
  96. package/dist/utils/transform-violations.d.ts +1 -1
  97. package/dist/utils/transform-violations.d.ts.map +1 -1
  98. package/dist/utils/transform-violations.js +18 -5
  99. package/dist/utils/transform-violations.js.map +1 -1
  100. package/dist/utils/update-elements-with-issues.d.ts +10 -4
  101. package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
  102. package/dist/utils/update-elements-with-issues.js +33 -6
  103. package/dist/utils/update-elements-with-issues.js.map +1 -1
  104. package/dist/validate-options.d.ts.map +1 -1
  105. package/dist/validate-options.js +86 -0
  106. package/dist/validate-options.js.map +1 -1
  107. package/package.json +8 -3
  108. package/src/accented.ts +10 -5
  109. package/src/constants.ts +1 -0
  110. package/src/dom-updater.ts +70 -24
  111. package/src/elements/accented-dialog.ts +88 -90
  112. package/src/elements/accented-trigger.ts +36 -12
  113. package/src/fullscreen-listener.ts +17 -0
  114. package/src/logger.ts +9 -1
  115. package/src/scanner.ts +37 -20
  116. package/src/state.ts +10 -2
  117. package/src/task-queue.ts +6 -4
  118. package/src/types.ts +55 -9
  119. package/src/utils/are-elements-with-issues-equal.ts +9 -0
  120. package/src/utils/containing-blocks.ts +57 -0
  121. package/src/utils/contains.test.ts +55 -0
  122. package/src/utils/contains.ts +19 -0
  123. package/src/utils/deduplicate-nodes.ts +3 -0
  124. package/src/utils/dom-helpers.ts +38 -0
  125. package/src/utils/ensure-non-empty.ts +6 -0
  126. package/src/utils/get-element-position.ts +28 -11
  127. package/src/utils/get-parent.ts +14 -0
  128. package/src/utils/get-scan-context.test.ts +79 -0
  129. package/src/utils/get-scan-context.ts +39 -0
  130. package/src/utils/get-scrollable-ancestors.ts +10 -5
  131. package/src/utils/is-node-in-scan-context.test.ts +70 -0
  132. package/src/utils/is-node-in-scan-context.ts +29 -0
  133. package/src/utils/normalize-context.test.ts +105 -0
  134. package/src/utils/normalize-context.ts +58 -0
  135. package/src/utils/shadow-dom-aware-mutation-observer.ts +78 -0
  136. package/src/utils/transform-violations.test.ts +10 -8
  137. package/src/utils/transform-violations.ts +20 -6
  138. package/src/utils/update-elements-with-issues.test.ts +102 -15
  139. package/src/utils/update-elements-with-issues.ts +51 -7
  140. package/src/validate-options.ts +88 -1
  141. package/dist/utils/is-html-element.d.ts +0 -2
  142. package/dist/utils/is-html-element.d.ts.map +0 -1
  143. package/dist/utils/is-html-element.js +0 -7
  144. package/dist/utils/is-html-element.js.map +0 -1
  145. 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 Node = Violation['nodes'][number];
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 = {getBoundingClientRect, isConnected: true};
50
+ const element1: HTMLElement = {...baseElement, isConnected: true};
38
51
  // @ts-expect-error element is not HTMLElement
39
- const element2: HTMLElement = {getBoundingClientRect, isConnected: true};
52
+ const element2: HTMLElement = {...baseElement, isConnected: true};
40
53
  // @ts-expect-error element is not HTMLElement
41
- const element3: HTMLElement = {getBoundingClientRect, isConnected: false};
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: Node = {
80
+ const node1: AxeNode = {
65
81
  ...commonNodeProps,
66
82
  element: element1,
67
83
  };
68
84
 
69
- const node2: Node = {
85
+ const node2: AxeNode = {
70
86
  ...commonNodeProps,
71
87
  element: element2,
72
88
  };
73
89
 
74
- const node3: Node = {
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(extendedElementsWithIssues, [violation1, violation2], win, 'accented');
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(extendedElementsWithIssues, [violation1, violation2, violation3], win, 'accented');
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(extendedElementsWithIssues, [violation1, violation2], win, 'accented');
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(extendedElementsWithIssues, [violation1, violation2], win, 'accented');
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(extendedElementsWithIssues, [violation1, violation4], win, 'accented');
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(extendedElementsWithIssues, [violation1], win, 'accented');
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(extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>>, violations: typeof AxeResults.violations, win: Window & { CSS: typeof CSS }, name: string) {
16
- const updatedElementsWithIssues = transformViolations(violations);
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.element === updatedElementWithIssues.element);
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.element === updatedElementWithIssues.element);
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
- return !updatedElementsWithIssues.some(updatedElementWithIssues => updatedElementWithIssues.element === extendedElementWithIssues.element);
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.element === extendedElementWithIssues.element);
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
  };
@@ -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,2 +0,0 @@
1
- export default function isHtmlElement(element: Element): element is HTMLElement;
2
- //# sourceMappingURL=is-html-element.d.ts.map
@@ -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
- }