accented 0.0.0-20250424114613 → 0.0.0-20250618181418

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 (201) hide show
  1. package/README.md +0 -209
  2. package/dist/accented.d.ts +2 -2
  3. package/dist/accented.d.ts.map +1 -1
  4. package/dist/accented.js +24 -20
  5. package/dist/accented.js.map +1 -1
  6. package/dist/common/tokens.d.ts +2 -0
  7. package/dist/common/tokens.d.ts.map +1 -0
  8. package/dist/common/tokens.js +2 -0
  9. package/dist/common/tokens.js.map +1 -0
  10. package/dist/dom-updater.d.ts +1 -1
  11. package/dist/dom-updater.d.ts.map +1 -1
  12. package/dist/dom-updater.js +14 -13
  13. package/dist/dom-updater.js.map +1 -1
  14. package/dist/elements/accented-dialog.d.ts +2 -3
  15. package/dist/elements/accented-dialog.d.ts.map +1 -1
  16. package/dist/elements/accented-dialog.js +14 -8
  17. package/dist/elements/accented-dialog.js.map +1 -1
  18. package/dist/elements/accented-trigger.d.ts +3 -4
  19. package/dist/elements/accented-trigger.d.ts.map +1 -1
  20. package/dist/elements/accented-trigger.js +8 -10
  21. package/dist/elements/accented-trigger.js.map +1 -1
  22. package/dist/fullscreen-listener.d.ts +1 -1
  23. package/dist/fullscreen-listener.d.ts.map +1 -1
  24. package/dist/fullscreen-listener.js +3 -4
  25. package/dist/fullscreen-listener.js.map +1 -1
  26. package/dist/intersection-observer.d.ts +1 -1
  27. package/dist/intersection-observer.d.ts.map +1 -1
  28. package/dist/intersection-observer.js +12 -6
  29. package/dist/intersection-observer.js.map +1 -1
  30. package/dist/log-and-rethrow.d.ts +1 -1
  31. package/dist/log-and-rethrow.d.ts.map +1 -1
  32. package/dist/log-and-rethrow.js +2 -3
  33. package/dist/log-and-rethrow.js.map +1 -1
  34. package/dist/logger.d.ts +1 -1
  35. package/dist/logger.d.ts.map +1 -1
  36. package/dist/logger.js +2 -2
  37. package/dist/logger.js.map +1 -1
  38. package/dist/register-elements.d.ts +1 -1
  39. package/dist/register-elements.d.ts.map +1 -1
  40. package/dist/register-elements.js +6 -7
  41. package/dist/register-elements.js.map +1 -1
  42. package/dist/resize-listener.d.ts +1 -1
  43. package/dist/resize-listener.d.ts.map +1 -1
  44. package/dist/resize-listener.js +3 -4
  45. package/dist/resize-listener.js.map +1 -1
  46. package/dist/scanner.d.ts +2 -2
  47. package/dist/scanner.d.ts.map +1 -1
  48. package/dist/scanner.js +25 -27
  49. package/dist/scanner.js.map +1 -1
  50. package/dist/scroll-listeners.d.ts +1 -1
  51. package/dist/scroll-listeners.d.ts.map +1 -1
  52. package/dist/scroll-listeners.js +3 -4
  53. package/dist/scroll-listeners.js.map +1 -1
  54. package/dist/state.d.ts +1 -1
  55. package/dist/state.d.ts.map +1 -1
  56. package/dist/state.js +4 -5
  57. package/dist/state.js.map +1 -1
  58. package/dist/task-queue.d.ts +2 -2
  59. package/dist/task-queue.d.ts.map +1 -1
  60. package/dist/task-queue.js +1 -1
  61. package/dist/task-queue.js.map +1 -1
  62. package/dist/types.d.ts +3 -3
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/utils/are-elements-with-issues-equal.d.ts +2 -2
  65. package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -1
  66. package/dist/utils/are-elements-with-issues-equal.js +3 -3
  67. package/dist/utils/are-elements-with-issues-equal.js.map +1 -1
  68. package/dist/utils/are-issue-sets-equal.d.ts +2 -2
  69. package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
  70. package/dist/utils/are-issue-sets-equal.js +3 -3
  71. package/dist/utils/are-issue-sets-equal.js.map +1 -1
  72. package/dist/utils/containing-blocks.d.ts.map +1 -1
  73. package/dist/utils/containing-blocks.js +1 -1
  74. package/dist/utils/containing-blocks.js.map +1 -1
  75. package/dist/utils/contains.d.ts +1 -1
  76. package/dist/utils/contains.d.ts.map +1 -1
  77. package/dist/utils/contains.js +1 -1
  78. package/dist/utils/contains.js.map +1 -1
  79. package/dist/utils/deduplicate-nodes.js +0 -1
  80. package/dist/utils/deduplicate-nodes.js.map +1 -1
  81. package/dist/utils/deep-merge.d.ts +1 -1
  82. package/dist/utils/deep-merge.d.ts.map +1 -1
  83. package/dist/utils/deep-merge.js +6 -5
  84. package/dist/utils/deep-merge.js.map +1 -1
  85. package/dist/utils/dom-helpers.d.ts.map +1 -1
  86. package/dist/utils/dom-helpers.js +4 -2
  87. package/dist/utils/dom-helpers.js.map +1 -1
  88. package/dist/utils/ensure-non-empty.d.ts +1 -1
  89. package/dist/utils/ensure-non-empty.d.ts.map +1 -1
  90. package/dist/utils/ensure-non-empty.js +2 -2
  91. package/dist/utils/ensure-non-empty.js.map +1 -1
  92. package/dist/utils/get-element-html.d.ts +1 -1
  93. package/dist/utils/get-element-html.d.ts.map +1 -1
  94. package/dist/utils/get-element-html.js +4 -2
  95. package/dist/utils/get-element-html.js.map +1 -1
  96. package/dist/utils/get-element-position.d.ts +2 -2
  97. package/dist/utils/get-element-position.d.ts.map +1 -1
  98. package/dist/utils/get-element-position.js +21 -25
  99. package/dist/utils/get-element-position.js.map +1 -1
  100. package/dist/utils/get-parent.d.ts +1 -1
  101. package/dist/utils/get-parent.d.ts.map +1 -1
  102. package/dist/utils/get-parent.js +1 -1
  103. package/dist/utils/get-parent.js.map +1 -1
  104. package/dist/utils/get-scan-context.d.ts +2 -2
  105. package/dist/utils/get-scan-context.d.ts.map +1 -1
  106. package/dist/utils/get-scan-context.js +9 -9
  107. package/dist/utils/get-scan-context.js.map +1 -1
  108. package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
  109. package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
  110. package/dist/utils/get-scrollable-ancestors.js +5 -5
  111. package/dist/utils/get-scrollable-ancestors.js.map +1 -1
  112. package/dist/utils/is-node-in-scan-context.d.ts +2 -2
  113. package/dist/utils/is-node-in-scan-context.d.ts.map +1 -1
  114. package/dist/utils/is-node-in-scan-context.js +5 -5
  115. package/dist/utils/is-node-in-scan-context.js.map +1 -1
  116. package/dist/utils/is-non-empty.d.ts +2 -0
  117. package/dist/utils/is-non-empty.d.ts.map +1 -0
  118. package/dist/utils/is-non-empty.js +4 -0
  119. package/dist/utils/is-non-empty.js.map +1 -0
  120. package/dist/utils/normalize-context.d.ts +2 -2
  121. package/dist/utils/normalize-context.d.ts.map +1 -1
  122. package/dist/utils/normalize-context.js +10 -8
  123. package/dist/utils/normalize-context.js.map +1 -1
  124. package/dist/utils/recalculate-positions.d.ts +1 -1
  125. package/dist/utils/recalculate-positions.d.ts.map +1 -1
  126. package/dist/utils/recalculate-positions.js +5 -5
  127. package/dist/utils/recalculate-positions.js.map +1 -1
  128. package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
  129. package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
  130. package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
  131. package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
  132. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +1 -1
  133. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
  134. package/dist/utils/shadow-dom-aware-mutation-observer.js +19 -22
  135. package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -1
  136. package/dist/utils/supports-anchor-positioning.d.ts +1 -1
  137. package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
  138. package/dist/utils/supports-anchor-positioning.js +1 -1
  139. package/dist/utils/supports-anchor-positioning.js.map +1 -1
  140. package/dist/utils/transform-violations.d.ts +2 -2
  141. package/dist/utils/transform-violations.d.ts.map +1 -1
  142. package/dist/utils/transform-violations.js +9 -9
  143. package/dist/utils/transform-violations.js.map +1 -1
  144. package/dist/utils/update-elements-with-issues.d.ts +3 -3
  145. package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
  146. package/dist/utils/update-elements-with-issues.js +34 -29
  147. package/dist/utils/update-elements-with-issues.js.map +1 -1
  148. package/dist/validate-options.d.ts +2 -2
  149. package/dist/validate-options.d.ts.map +1 -1
  150. package/dist/validate-options.js +24 -23
  151. package/dist/validate-options.js.map +1 -1
  152. package/package.json +5 -3
  153. package/src/accented.test.ts +2 -2
  154. package/src/accented.ts +34 -26
  155. package/src/common/tokens.ts +1 -0
  156. package/src/dom-updater.ts +26 -19
  157. package/src/elements/accented-dialog.ts +69 -43
  158. package/src/elements/accented-trigger.ts +52 -43
  159. package/src/fullscreen-listener.ts +15 -11
  160. package/src/intersection-observer.ts +27 -16
  161. package/src/log-and-rethrow.ts +2 -3
  162. package/src/logger.ts +8 -6
  163. package/src/register-elements.ts +7 -7
  164. package/src/resize-listener.ts +15 -11
  165. package/src/scanner.ts +55 -41
  166. package/src/scroll-listeners.ts +27 -19
  167. package/src/state.ts +24 -21
  168. package/src/task-queue.test.ts +5 -4
  169. package/src/task-queue.ts +2 -2
  170. package/src/types.ts +52 -53
  171. package/src/utils/are-elements-with-issues-equal.ts +7 -5
  172. package/src/utils/are-issue-sets-equal.test.ts +10 -6
  173. package/src/utils/are-issue-sets-equal.ts +8 -6
  174. package/src/utils/containing-blocks.ts +6 -3
  175. package/src/utils/contains.test.ts +2 -2
  176. package/src/utils/contains.ts +1 -1
  177. package/src/utils/deduplicate-nodes.ts +1 -1
  178. package/src/utils/deep-merge.test.ts +8 -1
  179. package/src/utils/deep-merge.ts +11 -8
  180. package/src/utils/dom-helpers.ts +6 -2
  181. package/src/utils/ensure-non-empty.ts +2 -2
  182. package/src/utils/get-element-html.ts +4 -2
  183. package/src/utils/get-element-position.ts +37 -24
  184. package/src/utils/get-parent.ts +1 -1
  185. package/src/utils/get-scan-context.test.ts +14 -8
  186. package/src/utils/get-scan-context.ts +12 -15
  187. package/src/utils/get-scrollable-ancestors.ts +8 -5
  188. package/src/utils/is-node-in-scan-context.test.ts +3 -3
  189. package/src/utils/is-node-in-scan-context.ts +6 -6
  190. package/src/utils/is-non-empty.ts +3 -0
  191. package/src/utils/normalize-context.test.ts +9 -9
  192. package/src/utils/normalize-context.ts +17 -10
  193. package/src/utils/recalculate-positions.ts +5 -5
  194. package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
  195. package/src/utils/shadow-dom-aware-mutation-observer.ts +21 -24
  196. package/src/utils/supports-anchor-positioning.ts +3 -3
  197. package/src/utils/transform-violations.test.ts +22 -20
  198. package/src/utils/transform-violations.ts +14 -10
  199. package/src/utils/update-elements-with-issues.test.ts +49 -49
  200. package/src/utils/update-elements-with-issues.ts +96 -71
  201. package/src/validate-options.ts +91 -38
@@ -1,9 +1,9 @@
1
- import type { AccentedDialog } from './accented-dialog';
2
- import type { Position } from '../types';
3
1
  import { effect } from '@preact/signals-core';
4
2
  import type { Signal } from '@preact/signals-core';
5
- import supportsAnchorPositioning from '../utils/supports-anchor-positioning.js';
6
- import logAndRethrow from '../log-and-rethrow.js';
3
+ import { logAndRethrow } from '../log-and-rethrow.js';
4
+ import type { Position } from '../types.ts';
5
+ import { supportsAnchorPositioning } from '../utils/supports-anchor-positioning.js';
6
+ import type { AccentedDialog } from './accented-dialog.ts';
7
7
 
8
8
  export interface AccentedTrigger extends HTMLElement {
9
9
  element: Element | undefined;
@@ -14,7 +14,7 @@ export interface AccentedTrigger extends HTMLElement {
14
14
 
15
15
  // We want Accented to not throw an error in Node, and use static imports,
16
16
  // so we can't export `class extends HTMLElement` because HTMLElement is not available in Node.
17
- export default (name: string) => {
17
+ export const getAccentedTrigger = (name: string) => {
18
18
  const template = document.createElement('template');
19
19
 
20
20
  // I initially tried creating a CSSStyelSheet object with styles instead of having a <style> element in the template,
@@ -33,8 +33,6 @@ export default (name: string) => {
33
33
  inset-block-start: anchor(self-start) !important;
34
34
  inset-block-end: anchor(self-end) !important;
35
35
 
36
- position-visibility: anchors-visible !important;
37
-
38
36
  /* Revert potential effects of white-space: pre; set on a trigger's ancestor. */
39
37
  white-space: normal !important;
40
38
 
@@ -105,8 +103,8 @@ export default (name: string) => {
105
103
  visible: Signal<boolean> | undefined;
106
104
 
107
105
  constructor() {
106
+ super();
108
107
  try {
109
- super();
110
108
  this.attachShadow({ mode: 'open' });
111
109
  const content = template.content.cloneNode(true);
112
110
  if (this.shadowRoot) {
@@ -138,42 +136,50 @@ export default (name: string) => {
138
136
 
139
137
  if (this.element) {
140
138
  this.#elementMutationObserver.observe(this.element, {
141
- attributes: true
139
+ attributes: true,
142
140
  });
143
141
  }
144
142
 
145
143
  this.#abortController = new AbortController();
146
- trigger?.addEventListener('click', (event) => {
147
- try {
148
- // event.preventDefault() ensures that if the issue is within a link,
149
- // the link's default behavior (following the URL) is prevented.
150
- event.preventDefault();
151
-
152
- // event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
153
- // (a link, or a button, or anything else), it doesn't get triggered.
154
- event.stopPropagation();
155
-
156
- // We append the dialog when the button is clicked,
157
- // and remove it from the DOM when the dialog is closed.
158
- // This gives us a performance improvement since Axe
159
- // scan time seems to depend on the number of elements in the DOM.
160
- if (this.dialog) {
161
- this.#dialogCloseAbortController = new AbortController();
162
- document.body.append(this.dialog);
163
- this.dialog.showModal();
164
- this.dialog.addEventListener('close', () => {
165
- try {
166
- this.dialog?.remove();
167
- this.#dialogCloseAbortController?.abort();
168
- } catch (error) {
169
- logAndRethrow(error);
170
- }
171
- }, { signal: this.#dialogCloseAbortController.signal });
144
+ trigger?.addEventListener(
145
+ 'click',
146
+ (event) => {
147
+ try {
148
+ // event.preventDefault() ensures that if the issue is within a link,
149
+ // the link's default behavior (following the URL) is prevented.
150
+ event.preventDefault();
151
+
152
+ // event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
153
+ // (a link, or a button, or anything else), it doesn't get triggered.
154
+ event.stopPropagation();
155
+
156
+ // We append the dialog when the button is clicked,
157
+ // and remove it from the DOM when the dialog is closed.
158
+ // This gives us a performance improvement since Axe
159
+ // scan time seems to depend on the number of elements in the DOM.
160
+ if (this.dialog) {
161
+ this.#dialogCloseAbortController = new AbortController();
162
+ document.body.append(this.dialog);
163
+ this.dialog.showModal();
164
+ this.dialog.addEventListener(
165
+ 'close',
166
+ () => {
167
+ try {
168
+ this.dialog?.remove();
169
+ this.#dialogCloseAbortController?.abort();
170
+ } catch (error) {
171
+ logAndRethrow(error);
172
+ }
173
+ },
174
+ { signal: this.#dialogCloseAbortController.signal },
175
+ );
176
+ }
177
+ } catch (error) {
178
+ logAndRethrow(error);
172
179
  }
173
- } catch (error) {
174
- logAndRethrow(error);
175
- }
176
- }, { signal: this.#abortController.signal });
180
+ },
181
+ { signal: this.#abortController.signal },
182
+ );
177
183
 
178
184
  if (!supportsAnchorPositioning(window)) {
179
185
  this.#disposeOfPositionEffect = effect(() => {
@@ -185,11 +191,14 @@ export default (name: string) => {
185
191
  this.style.setProperty('height', `${position.height}px`, 'important');
186
192
  }
187
193
  });
188
-
189
- this.#disposeOfVisibilityEffect = effect(() => {
190
- this.style.setProperty('visibility', this.visible?.value ? 'visible' : 'hidden', 'important');
191
- });
192
194
  }
195
+ this.#disposeOfVisibilityEffect = effect(() => {
196
+ this.style.setProperty(
197
+ 'visibility',
198
+ this.visible?.value ? 'visible' : 'hidden',
199
+ 'important',
200
+ );
201
+ });
193
202
  }
194
203
  } catch (error) {
195
204
  logAndRethrow(error);
@@ -1,17 +1,21 @@
1
- import logAndRethrow from './log-and-rethrow.js';
2
- import recalculatePositions from './utils/recalculate-positions.js';
1
+ import { logAndRethrow } from './log-and-rethrow.js';
2
+ import { recalculatePositions } from './utils/recalculate-positions.js';
3
3
 
4
- export default function setupResizeListener() {
4
+ export function setupResizeListener() {
5
5
  const abortController = new AbortController();
6
- window.addEventListener('fullscreenchange', () => {
7
- try {
8
- recalculatePositions();
9
- } catch (error) {
10
- logAndRethrow(error);
11
- }
12
- }, { signal: abortController.signal });
6
+ window.addEventListener(
7
+ 'fullscreenchange',
8
+ () => {
9
+ try {
10
+ recalculatePositions();
11
+ } catch (error) {
12
+ logAndRethrow(error);
13
+ }
14
+ },
15
+ { signal: abortController.signal },
16
+ );
13
17
 
14
18
  return () => {
15
19
  abortController.abort();
16
20
  };
17
- };
21
+ }
@@ -1,28 +1,39 @@
1
- import logAndRethrow from './log-and-rethrow.js';
1
+ import { logAndRethrow } from './log-and-rethrow.js';
2
2
  import { extendedElementsWithIssues } from './state.js';
3
- import getElementPosition from './utils/get-element-position.js';
3
+ import { getElementPosition } from './utils/get-element-position.js';
4
+ import { supportsAnchorPositioning } from './utils/supports-anchor-positioning.js';
4
5
 
5
- export default function setupIntersectionObserver() {
6
- const intersectionObserver = new IntersectionObserver((entries) => {
7
- try {
8
- for (const entry of entries) {
9
- const extendedElementWithIssues = extendedElementsWithIssues.value.find(el => el.element === entry.target);
10
- if (extendedElementWithIssues) {
11
- extendedElementWithIssues.visible.value = entry.isIntersecting;
12
- if (entry.isIntersecting) {
13
- extendedElementWithIssues.position.value = getElementPosition(entry.target, window);
6
+ export function setupIntersectionObserver() {
7
+ const intersectionObserver = new IntersectionObserver(
8
+ (entries) => {
9
+ try {
10
+ for (const entry of entries) {
11
+ const extendedElementWithIssues = extendedElementsWithIssues.value.find(
12
+ (el) => el.element === entry.target,
13
+ );
14
+ if (extendedElementWithIssues) {
15
+ // We initially treated setting visibility in the intersection observer
16
+ // as a fallback option for browsers that don't support `position-visibility`,
17
+ // but then we realized that this `position-visibility` actually works
18
+ // in an unexpected way when the container has `overflow: visible`.
19
+ // So now we always set visibility in the intersection observer.
20
+ extendedElementWithIssues.visible.value = entry.isIntersecting;
21
+ if (entry.isIntersecting && !supportsAnchorPositioning(window)) {
22
+ extendedElementWithIssues.position.value = getElementPosition(entry.target, window);
23
+ }
14
24
  }
15
25
  }
26
+ } catch (error) {
27
+ logAndRethrow(error);
16
28
  }
17
- } catch (error) {
18
- logAndRethrow(error);
19
- }
20
- }, { threshold: 0 });
29
+ },
30
+ { threshold: 0 },
31
+ );
21
32
 
22
33
  return {
23
34
  intersectionObserver,
24
35
  disconnect: () => {
25
36
  intersectionObserver.disconnect();
26
- }
37
+ },
27
38
  };
28
39
  }
@@ -1,9 +1,8 @@
1
1
  import { issuesUrl } from './constants.js';
2
2
 
3
- export default function logAndRethrow(error: unknown) {
3
+ export function logAndRethrow(error: unknown) {
4
4
  console.error(
5
- `Accented threw an error (see below). Try updating your browser to the latest version. ` +
6
- `If you’re still seeing the error, file an issue at ${issuesUrl}.`
5
+ `Accented threw an error (see below). Try updating your browser to the latest version. If you’re still seeing the error, file an issue at ${issuesUrl}.`,
7
6
  );
8
7
  throw error;
9
8
  }
package/src/logger.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { effect } from '@preact/signals-core';
2
- import { elementsWithIssues, enabled } from './state.js';
3
2
  import { accentedUrl } from './constants.js';
4
- import type { ElementWithIssues } from './types';
3
+ import { elementsWithIssues, enabled } from './state.js';
4
+ import type { ElementWithIssues } from './types.ts';
5
5
 
6
6
  function filterPropsForOutput(elements: Array<ElementWithIssues>) {
7
7
  return elements.map(({ element, issues }) => ({ element, issues }));
8
8
  }
9
9
 
10
- export default function createLogger() {
11
-
10
+ export function createLogger() {
12
11
  let firstRun = true;
13
12
 
14
13
  return effect(() => {
@@ -18,10 +17,13 @@ export default function createLogger() {
18
17
 
19
18
  const elementCount = elementsWithIssues.value.length;
20
19
  if (elementCount > 0) {
21
- const issueCount = elementsWithIssues.value.reduce((acc, { issues }) => acc + issues.length, 0);
20
+ const issueCount = elementsWithIssues.value.reduce(
21
+ (acc, { issues }) => acc + issues.length,
22
+ 0,
23
+ );
22
24
  console.log(
23
25
  `${issueCount} accessibility issue${issueCount === 1 ? '' : 's'} found in ${elementCount} element${issueCount === 1 ? '' : 's'} (Accented, ${accentedUrl}):\n`,
24
- filterPropsForOutput(elementsWithIssues.value)
26
+ filterPropsForOutput(elementsWithIssues.value),
25
27
  );
26
28
  } else {
27
29
  if (firstRun) {
@@ -1,16 +1,16 @@
1
- import getAccentedTrigger from './elements/accented-trigger.js';
2
- import getAccentedDialog from './elements/accented-dialog.js';
1
+ import { getAccentedDialog } from './elements/accented-dialog.js';
2
+ import { getAccentedTrigger } from './elements/accented-trigger.js';
3
3
 
4
- export default function registerElements(name: string): void {
4
+ export function registerElements(name: string): void {
5
5
  const elements = [
6
6
  {
7
7
  elementName: `${name}-trigger`,
8
- Component: getAccentedTrigger(name)
8
+ Component: getAccentedTrigger(name),
9
9
  },
10
10
  {
11
11
  elementName: `${name}-dialog`,
12
- Component: getAccentedDialog()
13
- }
12
+ Component: getAccentedDialog(),
13
+ },
14
14
  ];
15
15
 
16
16
  for (const { elementName, Component } of elements) {
@@ -18,4 +18,4 @@ export default function registerElements(name: string): void {
18
18
  customElements.define(elementName, Component);
19
19
  }
20
20
  }
21
- };
21
+ }
@@ -1,17 +1,21 @@
1
- import logAndRethrow from './log-and-rethrow.js';
2
- import recalculatePositions from './utils/recalculate-positions.js';
1
+ import { logAndRethrow } from './log-and-rethrow.js';
2
+ import { recalculatePositions } from './utils/recalculate-positions.js';
3
3
 
4
- export default function setupResizeListener() {
4
+ export function setupResizeListener() {
5
5
  const abortController = new AbortController();
6
- window.addEventListener('resize', () => {
7
- try {
8
- recalculatePositions();
9
- } catch (error) {
10
- logAndRethrow(error);
11
- }
12
- }, { signal: abortController.signal });
6
+ window.addEventListener(
7
+ 'resize',
8
+ () => {
9
+ try {
10
+ recalculatePositions();
11
+ } catch (error) {
12
+ logAndRethrow(error);
13
+ }
14
+ },
15
+ { signal: abortController.signal },
16
+ );
13
17
 
14
18
  return () => {
15
19
  abortController.abort();
16
20
  };
17
- };
21
+ }
package/src/scanner.ts CHANGED
@@ -1,19 +1,25 @@
1
1
  import axe from 'axe-core';
2
- import TaskQueue from './task-queue.js';
3
- import { elementsWithIssues, enabled, extendedElementsWithIssues } from './state.js';
4
- import type { AxeOptions, Throttle, Callback, Context } from './types';
5
- import updateElementsWithIssues from './utils/update-elements-with-issues.js';
6
- import recalculatePositions from './utils/recalculate-positions.js';
7
- import recalculateScrollableAncestors from './utils/recalculate-scrollable-ancestors.js';
8
- import supportsAnchorPositioning from './utils/supports-anchor-positioning.js';
9
2
  import { getAccentedElementNames, issuesUrl } from './constants.js';
10
- import logAndRethrow from './log-and-rethrow.js';
11
- import createShadowDOMAwareMutationObserver from './utils/shadow-dom-aware-mutation-observer.js';
12
- import getScanContext from './utils/get-scan-context.js';
13
-
14
- export default function createScanner(name: string, context: Context, axeOptions: AxeOptions, throttle: Required<Throttle>, callback: Callback) {
3
+ import { logAndRethrow } from './log-and-rethrow.js';
4
+ import { elementsWithIssues, enabled, extendedElementsWithIssues } from './state.js';
5
+ import { TaskQueue } from './task-queue.js';
6
+ import type { AxeOptions, Callback, Context, Throttle } from './types.ts';
7
+ import { getScanContext } from './utils/get-scan-context.js';
8
+ import { recalculatePositions } from './utils/recalculate-positions.js';
9
+ import { recalculateScrollableAncestors } from './utils/recalculate-scrollable-ancestors.js';
10
+ import { createShadowDOMAwareMutationObserver } from './utils/shadow-dom-aware-mutation-observer.js';
11
+ import { supportsAnchorPositioning } from './utils/supports-anchor-positioning.js';
12
+ import { updateElementsWithIssues } from './utils/update-elements-with-issues.js';
13
+
14
+ export function createScanner(
15
+ name: string,
16
+ context: Context,
17
+ axeOptions: AxeOptions,
18
+ throttle: Required<Throttle>,
19
+ callback: Callback,
20
+ ) {
15
21
  const axeRunningWindowProp = `__${name}_axe_running__`;
16
- const win: Record<string, any> = window;
22
+ const win = window as unknown as Record<string, boolean>;
17
23
  const taskQueue = new TaskQueue<Node>(async (nodes) => {
18
24
  // We may see errors coming from axe-core when Accented is toggled off and on in qiuck succession,
19
25
  // which I've seen happen with hot reloading of a React application.
@@ -23,14 +29,13 @@ export default function createScanner(name: string, context: Context, axeOptions
23
29
  }
24
30
 
25
31
  try {
26
-
27
32
  performance.mark('scan-start');
28
33
 
29
34
  win[axeRunningWindowProp] = true;
30
35
 
31
36
  const scanContext = getScanContext(nodes, context);
32
37
 
33
- let result;
38
+ let result: axe.AxeResults | undefined;
34
39
 
35
40
  try {
36
41
  result = await axe.run(scanContext, {
@@ -43,23 +48,20 @@ export default function createScanner(name: string, context: Context, axeOptions
43
48
  // A consumer of Accented can instead scan the iframed document by calling Accented initialization from that document.
44
49
  iframes: false,
45
50
  resultTypes: ['violations'],
46
- ...axeOptions
51
+ ...axeOptions,
47
52
  });
48
53
  } catch (error) {
49
54
  console.error(
50
- 'Accented: axe-core (the accessibility testing engine) threw an error. ' +
51
- 'Check the `axeOptions` property that you’re passing to Accented. ' +
52
- `If you still think it’s a bug in Accented, file an issue at ${issuesUrl}.\n`,
53
- error
55
+ `Accented: axe-core (the accessibility testing engine) threw an error. Check the \`axeOptions\` property that you’re passing to Accented. If you still think it’s a bug in Accented, file an issue at ${issuesUrl}.\n`,
56
+ error,
54
57
  );
55
- result = { violations: [] };
56
58
  }
57
59
  win[axeRunningWindowProp] = false;
58
60
 
59
61
  const scanMeasure = performance.measure('scan', 'scan-start');
60
62
  const scanDuration = Math.round(scanMeasure.duration);
61
63
 
62
- if (!enabled.value) {
64
+ if (!enabled.value || !result) {
63
65
  return;
64
66
  }
65
67
 
@@ -70,7 +72,7 @@ export default function createScanner(name: string, context: Context, axeOptions
70
72
  scanContext,
71
73
  violations: result.violations,
72
74
  win: window,
73
- name
75
+ name,
74
76
  });
75
77
 
76
78
  const domUpdateMeasure = performance.measure('dom-update', 'dom-update-start');
@@ -85,8 +87,8 @@ export default function createScanner(name: string, context: Context, axeOptions
85
87
  // Assuming that the {include, exclude} shape of the context object will be used less often
86
88
  // than other variants, we'll output just the `include` array in case nothing is excluded
87
89
  // in the scan.
88
- scanContext: scanContext.exclude.length > 0 ? scanContext : scanContext.include
89
- }
90
+ scanContext: scanContext.exclude.length > 0 ? scanContext : scanContext.include,
91
+ },
90
92
  });
91
93
  } catch (error) {
92
94
  win[axeRunningWindowProp] = false;
@@ -97,15 +99,21 @@ export default function createScanner(name: string, context: Context, axeOptions
97
99
  taskQueue.add(document);
98
100
 
99
101
  const accentedElementNames = getAccentedElementNames(name);
100
- const mutationObserver = createShadowDOMAwareMutationObserver(name, mutationList => {
102
+ const mutationObserver = createShadowDOMAwareMutationObserver(name, (mutationList) => {
101
103
  try {
102
104
  // We're not interested in mutations that are caused exclusively by the custom elements
103
105
  // introduced by Accented.
104
- const listWithoutAccentedElements = mutationList.filter(mutationRecord => {
105
- const onlyAccentedElementsAddedOrRemoved = mutationRecord.type === 'childList' &&
106
- [...mutationRecord.addedNodes].every(node => accentedElementNames.includes(node.nodeName.toLowerCase())) &&
107
- [...mutationRecord.removedNodes].every(node => accentedElementNames.includes(node.nodeName.toLowerCase()));
108
- const accentedElementChanged = mutationRecord.type === 'attributes' &&
106
+ const listWithoutAccentedElements = mutationList.filter((mutationRecord) => {
107
+ const onlyAccentedElementsAddedOrRemoved =
108
+ mutationRecord.type === 'childList' &&
109
+ [...mutationRecord.addedNodes].every((node) =>
110
+ accentedElementNames.includes(node.nodeName.toLowerCase()),
111
+ ) &&
112
+ [...mutationRecord.removedNodes].every((node) =>
113
+ accentedElementNames.includes(node.nodeName.toLowerCase()),
114
+ );
115
+ const accentedElementChanged =
116
+ mutationRecord.type === 'attributes' &&
109
117
  accentedElementNames.includes(mutationRecord.target.nodeName.toLowerCase());
110
118
  return !(onlyAccentedElementsAddedOrRemoved || accentedElementChanged);
111
119
  });
@@ -125,18 +133,24 @@ export default function createScanner(name: string, context: Context, axeOptions
125
133
  // If we simply exclude all mutations where attributeName = `data-${name}`,
126
134
  // we may miss other mutations on those same elements caused by Accented,
127
135
  // leading to extra runs of the mutation observer.
128
- const elementsWithAccentedAttributeChanges = listWithoutAccentedElements.reduce((nodes, mutationRecord) => {
129
- if (mutationRecord.type === 'attributes' && mutationRecord.attributeName === `data-${name}`) {
130
- nodes.add(mutationRecord.target);
131
- }
132
- return nodes;
133
- }, new Set<Node>());
134
-
135
- const filteredMutationList = listWithoutAccentedElements.filter(mutationRecord => {
136
+ const elementsWithAccentedAttributeChanges = listWithoutAccentedElements.reduce(
137
+ (nodes, mutationRecord) => {
138
+ if (
139
+ mutationRecord.type === 'attributes' &&
140
+ mutationRecord.attributeName === `data-${name}`
141
+ ) {
142
+ nodes.add(mutationRecord.target);
143
+ }
144
+ return nodes;
145
+ },
146
+ new Set<Node>(),
147
+ );
148
+
149
+ const filteredMutationList = listWithoutAccentedElements.filter((mutationRecord) => {
136
150
  return !elementsWithAccentedAttributeChanges.has(mutationRecord.target);
137
151
  });
138
152
 
139
- const nodes = filteredMutationList.map(mutationRecord => mutationRecord.target);
153
+ const nodes = filteredMutationList.map((mutationRecord) => mutationRecord.target);
140
154
  taskQueue.addMultiple(nodes);
141
155
  } catch (error) {
142
156
  logAndRethrow(error);
@@ -147,7 +161,7 @@ export default function createScanner(name: string, context: Context, axeOptions
147
161
  subtree: true,
148
162
  childList: true,
149
163
  attributes: true,
150
- characterData: true
164
+ characterData: true,
151
165
  });
152
166
 
153
167
  return () => {
@@ -1,37 +1,45 @@
1
1
  import { effect } from '@preact/signals-core';
2
- import recalculatePositions from './utils/recalculate-positions.js';
2
+ import { logAndRethrow } from './log-and-rethrow.js';
3
3
  import { scrollableAncestors } from './state.js';
4
- import logAndRethrow from './log-and-rethrow.js';
4
+ import { recalculatePositions } from './utils/recalculate-positions.js';
5
5
 
6
- export default function setupScrollListeners() {
6
+ export function setupScrollListeners() {
7
7
  const documentAbortController = new AbortController();
8
- document.addEventListener('scroll', () => {
9
- try {
10
- recalculatePositions();
11
- } catch (error) {
12
- logAndRethrow(error);
13
- }
14
- }, { signal: documentAbortController.signal });
8
+ document.addEventListener(
9
+ 'scroll',
10
+ () => {
11
+ try {
12
+ recalculatePositions();
13
+ } catch (error) {
14
+ logAndRethrow(error);
15
+ }
16
+ },
17
+ { signal: documentAbortController.signal },
18
+ );
15
19
 
16
20
  const disposeOfEffect = effect(() => {
17
21
  // TODO: optimize performance, issue #81
18
22
  const elementAbortController = new AbortController();
19
23
  for (const scrollableAncestor of scrollableAncestors.value) {
20
- scrollableAncestor.addEventListener('scroll', () => {
21
- try {
22
- recalculatePositions();
23
- } catch (error) {
24
- logAndRethrow(error);
25
- }
26
- }, { signal: elementAbortController.signal });
24
+ scrollableAncestor.addEventListener(
25
+ 'scroll',
26
+ () => {
27
+ try {
28
+ recalculatePositions();
29
+ } catch (error) {
30
+ logAndRethrow(error);
31
+ }
32
+ },
33
+ { signal: elementAbortController.signal },
34
+ );
27
35
  }
28
36
  return () => {
29
37
  elementAbortController.abort();
30
- }
38
+ };
31
39
  });
32
40
 
33
41
  return () => {
34
42
  documentAbortController.abort();
35
43
  disposeOfEffect();
36
44
  };
37
- };
45
+ }
package/src/state.ts CHANGED
@@ -1,32 +1,35 @@
1
- import { signal, computed } from '@preact/signals-core';
1
+ import { computed, signal } from '@preact/signals-core';
2
2
 
3
- import type { ElementWithIssues, ExtendedElementWithIssues } from './types';
3
+ import type { ElementWithIssues, ExtendedElementWithIssues } from './types.ts';
4
4
 
5
5
  export const enabled = signal(false);
6
6
 
7
7
  export const extendedElementsWithIssues = signal<Array<ExtendedElementWithIssues>>([]);
8
8
 
9
- export const elementsWithIssues = computed<Array<ElementWithIssues>>(() => extendedElementsWithIssues.value.map(extendedElementWithIssues => ({
10
- element: extendedElementWithIssues.element,
11
- rootNode: extendedElementWithIssues.rootNode,
12
- issues: extendedElementWithIssues.issues.value
13
- })));
9
+ export const elementsWithIssues = computed<Array<ElementWithIssues>>(() =>
10
+ extendedElementsWithIssues.value.map((extendedElementWithIssues) => ({
11
+ element: extendedElementWithIssues.element,
12
+ rootNode: extendedElementWithIssues.rootNode,
13
+ issues: extendedElementWithIssues.issues.value,
14
+ })),
15
+ );
14
16
 
15
- export const rootNodes = computed<Set<Node>>(() =>
16
- new Set(
17
- (enabled.value ? [document as Node] : [])
18
- .concat(...(extendedElementsWithIssues.value.map(extendedElementWithIssues => extendedElementWithIssues.rootNode)))
19
- )
17
+ export const rootNodes = computed<Set<Node>>(
18
+ () =>
19
+ new Set(
20
+ (enabled.value ? [document as Node] : []).concat(
21
+ ...extendedElementsWithIssues.value.map(
22
+ (extendedElementWithIssues) => extendedElementWithIssues.rootNode,
23
+ ),
24
+ ),
25
+ ),
20
26
  );
21
27
 
22
28
  export const scrollableAncestors = computed<Set<Element>>(() =>
23
- extendedElementsWithIssues.value.reduce(
24
- (scrollableAncestors, extendedElementWithIssues) => {
25
- for (const scrollableAncestor of extendedElementWithIssues.scrollableAncestors.value) {
26
- scrollableAncestors.add(scrollableAncestor);
27
- }
28
- return scrollableAncestors;
29
- },
30
- new Set<Element>()
31
- )
29
+ extendedElementsWithIssues.value.reduce((scrollableAncestors, extendedElementWithIssues) => {
30
+ for (const scrollableAncestor of extendedElementWithIssues.scrollableAncestors.value) {
31
+ scrollableAncestors.add(scrollableAncestor);
32
+ }
33
+ return scrollableAncestors;
34
+ }, new Set<Element>()),
32
35
  );