accented 0.0.0-20250424114613 → 0.0.0-20250701143712

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 (207) hide show
  1. package/README.md +44 -193
  2. package/dist/accented.d.ts +7 -7
  3. package/dist/accented.d.ts.map +1 -1
  4. package/dist/accented.js +30 -27
  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/constants.d.ts +1 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/constants.js.map +1 -1
  14. package/dist/dom-updater.d.ts +1 -1
  15. package/dist/dom-updater.d.ts.map +1 -1
  16. package/dist/dom-updater.js +14 -13
  17. package/dist/dom-updater.js.map +1 -1
  18. package/dist/elements/accented-dialog.d.ts +2 -3
  19. package/dist/elements/accented-dialog.d.ts.map +1 -1
  20. package/dist/elements/accented-dialog.js +14 -8
  21. package/dist/elements/accented-dialog.js.map +1 -1
  22. package/dist/elements/accented-trigger.d.ts +3 -4
  23. package/dist/elements/accented-trigger.d.ts.map +1 -1
  24. package/dist/elements/accented-trigger.js +10 -10
  25. package/dist/elements/accented-trigger.js.map +1 -1
  26. package/dist/fullscreen-listener.d.ts +1 -1
  27. package/dist/fullscreen-listener.d.ts.map +1 -1
  28. package/dist/fullscreen-listener.js +3 -4
  29. package/dist/fullscreen-listener.js.map +1 -1
  30. package/dist/intersection-observer.d.ts +1 -1
  31. package/dist/intersection-observer.d.ts.map +1 -1
  32. package/dist/intersection-observer.js +12 -6
  33. package/dist/intersection-observer.js.map +1 -1
  34. package/dist/log-and-rethrow.d.ts +1 -1
  35. package/dist/log-and-rethrow.d.ts.map +1 -1
  36. package/dist/log-and-rethrow.js +2 -3
  37. package/dist/log-and-rethrow.js.map +1 -1
  38. package/dist/logger.d.ts +1 -1
  39. package/dist/logger.d.ts.map +1 -1
  40. package/dist/logger.js +2 -2
  41. package/dist/logger.js.map +1 -1
  42. package/dist/register-elements.d.ts +1 -1
  43. package/dist/register-elements.d.ts.map +1 -1
  44. package/dist/register-elements.js +6 -7
  45. package/dist/register-elements.js.map +1 -1
  46. package/dist/resize-listener.d.ts +1 -1
  47. package/dist/resize-listener.d.ts.map +1 -1
  48. package/dist/resize-listener.js +3 -4
  49. package/dist/resize-listener.js.map +1 -1
  50. package/dist/scanner.d.ts +2 -2
  51. package/dist/scanner.d.ts.map +1 -1
  52. package/dist/scanner.js +36 -36
  53. package/dist/scanner.js.map +1 -1
  54. package/dist/scroll-listeners.d.ts +1 -1
  55. package/dist/scroll-listeners.d.ts.map +1 -1
  56. package/dist/scroll-listeners.js +3 -4
  57. package/dist/scroll-listeners.js.map +1 -1
  58. package/dist/state.d.ts +1 -1
  59. package/dist/state.d.ts.map +1 -1
  60. package/dist/state.js +4 -5
  61. package/dist/state.js.map +1 -1
  62. package/dist/task-queue.d.ts +2 -2
  63. package/dist/task-queue.d.ts.map +1 -1
  64. package/dist/task-queue.js +1 -1
  65. package/dist/task-queue.js.map +1 -1
  66. package/dist/types.d.ts +102 -46
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js.map +1 -1
  69. package/dist/utils/are-elements-with-issues-equal.d.ts +2 -2
  70. package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -1
  71. package/dist/utils/are-elements-with-issues-equal.js +3 -3
  72. package/dist/utils/are-elements-with-issues-equal.js.map +1 -1
  73. package/dist/utils/are-issue-sets-equal.d.ts +2 -2
  74. package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
  75. package/dist/utils/are-issue-sets-equal.js +3 -3
  76. package/dist/utils/are-issue-sets-equal.js.map +1 -1
  77. package/dist/utils/containing-blocks.d.ts.map +1 -1
  78. package/dist/utils/containing-blocks.js +1 -1
  79. package/dist/utils/containing-blocks.js.map +1 -1
  80. package/dist/utils/contains.d.ts +1 -1
  81. package/dist/utils/contains.d.ts.map +1 -1
  82. package/dist/utils/contains.js +1 -1
  83. package/dist/utils/contains.js.map +1 -1
  84. package/dist/utils/deduplicate-nodes.js +0 -1
  85. package/dist/utils/deduplicate-nodes.js.map +1 -1
  86. package/dist/utils/deep-merge.d.ts +1 -1
  87. package/dist/utils/deep-merge.d.ts.map +1 -1
  88. package/dist/utils/deep-merge.js +8 -5
  89. package/dist/utils/deep-merge.js.map +1 -1
  90. package/dist/utils/dom-helpers.d.ts.map +1 -1
  91. package/dist/utils/dom-helpers.js +4 -2
  92. package/dist/utils/dom-helpers.js.map +1 -1
  93. package/dist/utils/ensure-non-empty.d.ts +1 -1
  94. package/dist/utils/ensure-non-empty.d.ts.map +1 -1
  95. package/dist/utils/ensure-non-empty.js +2 -2
  96. package/dist/utils/ensure-non-empty.js.map +1 -1
  97. package/dist/utils/get-element-html.d.ts +1 -1
  98. package/dist/utils/get-element-html.d.ts.map +1 -1
  99. package/dist/utils/get-element-html.js +4 -2
  100. package/dist/utils/get-element-html.js.map +1 -1
  101. package/dist/utils/get-element-position.d.ts +2 -2
  102. package/dist/utils/get-element-position.d.ts.map +1 -1
  103. package/dist/utils/get-element-position.js +21 -25
  104. package/dist/utils/get-element-position.js.map +1 -1
  105. package/dist/utils/get-parent.d.ts +1 -1
  106. package/dist/utils/get-parent.d.ts.map +1 -1
  107. package/dist/utils/get-parent.js +1 -1
  108. package/dist/utils/get-parent.js.map +1 -1
  109. package/dist/utils/get-scan-context.d.ts +2 -2
  110. package/dist/utils/get-scan-context.d.ts.map +1 -1
  111. package/dist/utils/get-scan-context.js +9 -9
  112. package/dist/utils/get-scan-context.js.map +1 -1
  113. package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
  114. package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
  115. package/dist/utils/get-scrollable-ancestors.js +5 -5
  116. package/dist/utils/get-scrollable-ancestors.js.map +1 -1
  117. package/dist/utils/is-node-in-scan-context.d.ts +2 -2
  118. package/dist/utils/is-node-in-scan-context.d.ts.map +1 -1
  119. package/dist/utils/is-node-in-scan-context.js +5 -5
  120. package/dist/utils/is-node-in-scan-context.js.map +1 -1
  121. package/dist/utils/is-non-empty.d.ts +2 -0
  122. package/dist/utils/is-non-empty.d.ts.map +1 -0
  123. package/dist/utils/is-non-empty.js +4 -0
  124. package/dist/utils/is-non-empty.js.map +1 -0
  125. package/dist/utils/normalize-context.d.ts +2 -2
  126. package/dist/utils/normalize-context.d.ts.map +1 -1
  127. package/dist/utils/normalize-context.js +10 -8
  128. package/dist/utils/normalize-context.js.map +1 -1
  129. package/dist/utils/recalculate-positions.d.ts +1 -1
  130. package/dist/utils/recalculate-positions.d.ts.map +1 -1
  131. package/dist/utils/recalculate-positions.js +5 -5
  132. package/dist/utils/recalculate-positions.js.map +1 -1
  133. package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
  134. package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
  135. package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
  136. package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
  137. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +1 -1
  138. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
  139. package/dist/utils/shadow-dom-aware-mutation-observer.js +19 -22
  140. package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -1
  141. package/dist/utils/supports-anchor-positioning.d.ts +1 -1
  142. package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
  143. package/dist/utils/supports-anchor-positioning.js +1 -1
  144. package/dist/utils/supports-anchor-positioning.js.map +1 -1
  145. package/dist/utils/transform-violations.d.ts +2 -2
  146. package/dist/utils/transform-violations.d.ts.map +1 -1
  147. package/dist/utils/transform-violations.js +9 -9
  148. package/dist/utils/transform-violations.js.map +1 -1
  149. package/dist/utils/update-elements-with-issues.d.ts +3 -3
  150. package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
  151. package/dist/utils/update-elements-with-issues.js +34 -29
  152. package/dist/utils/update-elements-with-issues.js.map +1 -1
  153. package/dist/validate-options.d.ts +2 -2
  154. package/dist/validate-options.d.ts.map +1 -1
  155. package/dist/validate-options.js +24 -23
  156. package/dist/validate-options.js.map +1 -1
  157. package/package.json +7 -4
  158. package/src/accented.test.ts +2 -2
  159. package/src/accented.ts +39 -32
  160. package/src/common/tokens.ts +1 -0
  161. package/src/constants.ts +1 -1
  162. package/src/dom-updater.ts +26 -19
  163. package/src/elements/accented-dialog.ts +69 -43
  164. package/src/elements/accented-trigger.ts +54 -43
  165. package/src/fullscreen-listener.ts +15 -11
  166. package/src/intersection-observer.ts +27 -16
  167. package/src/log-and-rethrow.ts +2 -3
  168. package/src/logger.ts +8 -6
  169. package/src/register-elements.ts +7 -7
  170. package/src/resize-listener.ts +15 -11
  171. package/src/scanner.ts +66 -50
  172. package/src/scroll-listeners.ts +27 -19
  173. package/src/state.ts +24 -21
  174. package/src/task-queue.test.ts +5 -4
  175. package/src/task-queue.ts +2 -2
  176. package/src/types.ts +151 -95
  177. package/src/utils/are-elements-with-issues-equal.ts +7 -5
  178. package/src/utils/are-issue-sets-equal.test.ts +10 -6
  179. package/src/utils/are-issue-sets-equal.ts +8 -6
  180. package/src/utils/containing-blocks.ts +6 -3
  181. package/src/utils/contains.test.ts +2 -2
  182. package/src/utils/contains.ts +1 -1
  183. package/src/utils/deduplicate-nodes.ts +1 -1
  184. package/src/utils/deep-merge.test.ts +8 -1
  185. package/src/utils/deep-merge.ts +14 -8
  186. package/src/utils/dom-helpers.ts +6 -2
  187. package/src/utils/ensure-non-empty.ts +2 -2
  188. package/src/utils/get-element-html.ts +4 -2
  189. package/src/utils/get-element-position.ts +37 -24
  190. package/src/utils/get-parent.ts +1 -1
  191. package/src/utils/get-scan-context.test.ts +14 -8
  192. package/src/utils/get-scan-context.ts +12 -15
  193. package/src/utils/get-scrollable-ancestors.ts +8 -5
  194. package/src/utils/is-node-in-scan-context.test.ts +3 -3
  195. package/src/utils/is-node-in-scan-context.ts +6 -6
  196. package/src/utils/is-non-empty.ts +3 -0
  197. package/src/utils/normalize-context.test.ts +9 -9
  198. package/src/utils/normalize-context.ts +17 -10
  199. package/src/utils/recalculate-positions.ts +5 -5
  200. package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
  201. package/src/utils/shadow-dom-aware-mutation-observer.ts +21 -24
  202. package/src/utils/supports-anchor-positioning.ts +3 -3
  203. package/src/utils/transform-violations.test.ts +22 -20
  204. package/src/utils/transform-violations.ts +14 -10
  205. package/src/utils/update-elements-with-issues.test.ts +49 -49
  206. package/src/utils/update-elements-with-issues.ts +96 -71
  207. package/src/validate-options.ts +91 -38
package/src/types.ts CHANGED
@@ -1,33 +1,45 @@
1
- import type axe from 'axe-core';
2
1
  import type { Signal } from '@preact/signals-core';
3
- import type { AccentedTrigger } from './elements/accented-trigger';
2
+ import type axe from 'axe-core';
3
+ import type { AccentedTrigger } from './elements/accented-trigger.ts';
4
4
 
5
5
  export type Throttle = {
6
6
  /**
7
- * The minimal time between scans.
7
+ * How long Accented must wait (in milliseconds) to run a scan after a mutation or after the previous scan (whichever finished last).
8
+ *
9
+ * If the page you’re scanning has a lot of nodes,
10
+ * scanning may take a noticeable time (~ a few hundred milliseconds),
11
+ * during which time the main thread will be blocked most of the time.
12
+ *
13
+ * You may want to experiment with this value if your page contents change frequently
14
+ * or if it has JavaScript-based animations running on the main thread.
8
15
  *
9
- * Default: `1000`.
16
+ * @default 1000
10
17
  * */
11
- wait?: number,
18
+ wait?: number;
12
19
 
13
20
  /**
14
- * When to run the scan on Accented initialization or on a mutation.
21
+ * If `leading` is set to `true`, the scan runs immediately after a mutation.
22
+ * In this case, `wait` only applies to subsequent scans,
23
+ * giving the page at least `wait` milliseconds between the end of the previous scan
24
+ * and the beginning of the next one.
15
25
  *
16
- * If `true`, the scan will run immediately. If `false`, the scan will run after the first throttle delay.
26
+ * If `leading` is set to `false`, the wait applies to mutations as well,
27
+ * delaying the output.
28
+ * This may be useful if you’re expecting quick bursts of mutations on your page.
17
29
  *
18
- * Default: `true`.
30
+ * @default true
19
31
  * */
20
- leading?: boolean
21
- }
32
+ leading?: boolean;
33
+ };
22
34
 
23
35
  export type Output = {
24
36
  /**
25
- * Whether to output the issues to the console.
37
+ * Whether the list of elements with issues should be printed to the browser console whenever issues are added, removed, or changed.
26
38
  *
27
- * Default: `true`.
39
+ * @default true
28
40
  * */
29
- console?: boolean
30
- }
41
+ console?: boolean;
42
+ };
31
43
 
32
44
  /**
33
45
  * Model context type based on axe.ElementContext,
@@ -43,67 +55,51 @@ export type SelectorList = Array<Selector> | NodeList;
43
55
  // The rest of the type is structured the same as in axe-core.
44
56
  export type ContextProp = Selector | SelectorList;
45
57
 
46
- export type ContextObject = {
47
- include: ContextProp;
48
- exclude?: ContextProp;
49
- } | {
50
- exclude: ContextProp;
51
- include?: ContextProp;
52
- };
58
+ export type ContextObject =
59
+ | {
60
+ include: ContextProp;
61
+ exclude?: ContextProp;
62
+ }
63
+ | {
64
+ exclude: ContextProp;
65
+ include?: ContextProp;
66
+ };
53
67
 
54
68
  export type Context = ContextProp | ContextObject;
55
69
 
56
-
57
-
58
70
  export const allowedAxeOptions = ['rules', 'runOnly'] as const;
59
71
 
60
- export type AxeOptions = Pick<axe.RunOptions, typeof allowedAxeOptions[number]>;
72
+ export type AxeOptions = Pick<axe.RunOptions, (typeof allowedAxeOptions)[number]>;
61
73
 
62
74
  type CallbackParams = {
63
75
  /**
64
- * The most current array of elements with issues.
76
+ * The most up-to-date array of all elements with accessibility issues.
65
77
  * */
66
- elementsWithIssues: Array<ElementWithIssues>,
78
+ elementsWithIssues: Array<ElementWithIssues>;
67
79
 
68
80
  /**
69
- * * `performance`: runtime performance of the last scan. An object:
70
- * * `totalBlockingTime`: how long the main thread was blocked by Accented during the last scan, in milliseconds.
81
+ * Runtime performance of the last scan. An object with the following props:
82
+ * - `totalBlockingTime`: how long the main thread was blocked by Accented during the last scan, in milliseconds.
71
83
  * It’s further divided into the `scan` and `domUpdate` phases.
72
- * * `scan`: how long the `scan` phase took, in milliseconds.
73
- * * `domUpdate`: how long the `domUpdate` phase took, in milliseconds.
74
- * * `scanContext`: nodes that got scanned. Either an array of nodes,
75
- * or an object with `include` and `exclude` properties (if any nodes were excluded).
84
+ * - `scan`: how long scanning (the execution of `axe.run()`) took, in milliseconds.
85
+ * - `domUpdate`: how long the DOM update (adding / removing outlines and dialog trigger buttons) took, in milliseconds.
76
86
  * */
77
87
  performance: {
78
- totalBlockingTime: number,
79
- scan: number,
80
- domUpdate: number,
81
- scanContext: ScanContext | Array<Node>
82
- }
83
- }
84
-
85
- export type Callback = (params: CallbackParams) => void;
86
-
87
- export type AccentedOptions = {
88
+ totalBlockingTime: number;
89
+ scan: number;
90
+ domUpdate: number;
91
+ };
88
92
 
89
93
  /**
90
- * The `context` parameter for `axe.run()`.
91
- *
92
- * Determines what element(s) to scan for accessibility issues.
93
- *
94
- * Accepts a variety of shapes:
95
- * * an element reference;
96
- * * a selector;
97
- * * a `NodeList`;
98
- * * an include / exclude object;
99
- * * and more.
100
- *
101
- * See documentation: https://www.deque.com/axe/core-documentation/api-documentation/#context-parameter
102
- *
103
- * Default: `document`.
94
+ * Nodes that got scanned. Either an array of nodes,
95
+ * or an object with `include` and `exclude` properties (if any nodes were excluded).
104
96
  */
105
- context?: Context,
97
+ scanContext: ScanContext | Array<Node>;
98
+ };
106
99
 
100
+ export type Callback = (params: CallbackParams) => void;
101
+
102
+ export type AccentedOptions = {
107
103
  /**
108
104
  * The `options` parameter for `axe.run()`.
109
105
  *
@@ -116,14 +112,73 @@ export type AccentedOptions = {
116
112
  *
117
113
  * See documentation: https://www.deque.com/axe/core-documentation/api-documentation/#options-parameter
118
114
  *
119
- * Default: `{}`.
115
+ * @default {}
116
+ */
117
+ axeOptions?: AxeOptions;
118
+
119
+ /**
120
+ * A function that will be called after each scan.
121
+ *
122
+ * Potential uses:
123
+
124
+ * - do something with the scan results,
125
+ * for example send them to a backend for storage and analysis;
126
+ * - analyze Accented’s performance.
127
+ *
128
+ * @default () => {}
129
+ *
130
+ * @example
131
+ *
132
+ * accented({
133
+ * callback: ({ elementsWithIssues, performance, scanContext }) => {
134
+ * console.log('Elements with issues:', elementsWithIssues);
135
+ * console.log('Total blocking time:', performance.totalBlockingTime);
136
+ * console.log('Scan context:', scanContext);
137
+ * }
138
+ * });
139
+ *
140
+ * */
141
+ callback?: Callback;
142
+
143
+ /**
144
+ * The `context` parameter for `axe.run()`.
145
+ *
146
+ * Determines what part(s) of the page to scan for accessibility issues.
147
+ *
148
+ * Accepts a variety of shapes:
149
+ *
150
+ * - a [`Node`](https://developer.mozilla.org/en-US/docs/Web/API/Node) (in practice it will likely be an instance of [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element), [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document), or [`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment));
151
+ * - a valid [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors);
152
+ * - an object for selecting elements within shadow DOM,
153
+ * whose shape is `{ fromShadowDom: [selector1, selector2, ...] }`,
154
+ * where `selector1`, `selector2`, etc. select shadow hosts, and the last selector selects the actual context.
155
+ * `selector2` in this example is _within_ the shadow root created on the element(s) that match `selector1`,
156
+ * so in practice you shouldn’t have more than two elements in such an array
157
+ * unless you have a very complex structure with multiple shadow DOM layers;
158
+ * - a [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) (likely a result of a [`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) call);
159
+ * - an array containing any combination of selectors, nodes, or shadow DOM objects (described above);
160
+ * - an object containing `include` and / or `exclude` properties.
161
+ * It’s useful if you’d like to exclude certain elements or parts of the page.
162
+ * The values for `include` and `exclude` can take any of the above shapes.
163
+ * It’s unlikely that you’d want to have complex `include` / `exclude` rules,
164
+ * but if you do, the exact behavior is documented by the relevant tests:
165
+ * [`is-node-in-scan-context.test.ts`](https://github.com/pomerantsev/accented/blob/main/packages/accented/src/utils/is-node-in-scan-context.test.ts).
166
+ *
167
+ * See also the documentation for the [`context` parameter of `axe.run()`](https://www.deque.com/axe/core-documentation/api-documentation/#context-parameter),
168
+ * which the `context` option from Accented mostly mirrors
169
+ * (note that Accented doesn’t support the `fromFrames` object shape).
170
+ *
171
+ * @default document
120
172
  */
121
- axeOptions?: AxeOptions,
173
+ context?: Context;
122
174
 
123
175
  /**
124
176
  * The character sequence that’s used in various elements, attributes and stylesheets that Accented adds to the page.
177
+ *
178
+ * You shouldn’t have to provide this prop unless some of the names on your page have "accented" in it and conflict with what Accented provides by default.
179
+ *
125
180
  * * The data attribute that’s added to elements with issues (default: `data-accented`).
126
- * * The custom elements for the button and the dialog that get created for each element with issues
181
+ * * The names of custom elements for the button and the dialog that get created for each element with issues
127
182
  * (default: `accented-trigger`, `accented-dialog`).
128
183
  * * The CSS cascade layer containing page-wide Accented-specific styles (default: `accented`).
129
184
  * * The prefix for some of the CSS custom properties used by Accented (default: `--accented-`).
@@ -133,26 +188,27 @@ export type AccentedOptions = {
133
188
  * Only lowercase alphanumeric characters and dashes (-) are allowed in the name,
134
189
  * and it must start with a lowercase letter.
135
190
  *
136
- * Default: `accented`.
191
+ * @default 'accented'
192
+ *
193
+ * @example
194
+ *
195
+ * accented({name: 'my-name'});
196
+ *
197
+ * With the above option provided, the attribute set on elements with issues will be `data-my-name`,
198
+ * a custom element will be called `my-name-trigger`, and so on.
199
+ *
137
200
  */
138
- name?: string,
139
-
140
- /**
141
- * Output options object.
142
- * */
143
- output?: Output,
201
+ name?: string;
144
202
 
145
203
  /**
146
- * Scan throttling options object.
204
+ * An object controlling how the results of scans are presented.
147
205
  * */
148
- throttle?: Throttle,
206
+ output?: Output;
149
207
 
150
208
  /**
151
- * A callback that will be called after each scan.
152
- *
153
- * Default: `() => {}`.
209
+ * An object controlling at what moments Accented will run its scans.
154
210
  * */
155
- callback?: Callback
211
+ throttle?: Throttle;
156
212
  };
157
213
 
158
214
  /**
@@ -162,41 +218,41 @@ export type AccentedOptions = {
162
218
  export type DisableAccented = () => void;
163
219
 
164
220
  export type Position = {
165
- left: number,
166
- top: number,
167
- width: number,
168
- height: number
221
+ left: number;
222
+ top: number;
223
+ width: number;
224
+ height: number;
169
225
  };
170
226
 
171
227
  export type Issue = {
172
- id: string,
173
- title: string,
174
- description: string,
175
- url: string,
176
- impact: axe.ImpactValue
228
+ id: string;
229
+ title: string;
230
+ description: string;
231
+ url: string;
232
+ impact: axe.ImpactValue;
177
233
  };
178
234
 
179
235
  export type BaseElementWithIssues = {
180
- element: HTMLElement | SVGElement,
181
- rootNode: Node
236
+ element: HTMLElement | SVGElement;
237
+ rootNode: Node;
182
238
  };
183
239
 
184
240
  export type ElementWithIssues = BaseElementWithIssues & {
185
- issues: Array<Issue>
241
+ issues: Array<Issue>;
186
242
  };
187
243
 
188
244
  export type ExtendedElementWithIssues = BaseElementWithIssues & {
189
- issues: Signal<ElementWithIssues['issues']>,
190
- visible: Signal<boolean>,
191
- trigger: AccentedTrigger,
192
- position: Signal<Position>,
193
- skipRender: boolean,
194
- anchorNameValue: string,
195
- scrollableAncestors: Signal<Set<Element>>
196
- id: number
245
+ issues: Signal<ElementWithIssues['issues']>;
246
+ visible: Signal<boolean>;
247
+ trigger: AccentedTrigger;
248
+ position: Signal<Position>;
249
+ skipRender: boolean;
250
+ anchorNameValue: string;
251
+ scrollableAncestors: Signal<Set<Element>>;
252
+ id: number;
197
253
  };
198
254
 
199
255
  export type ScanContext = {
200
- include: Array<Node>,
201
- exclude: Array<Node>
256
+ include: Array<Node>;
257
+ exclude: Array<Node>;
202
258
  };
@@ -1,9 +1,11 @@
1
- import type { BaseElementWithIssues } from "../types";
1
+ import type { BaseElementWithIssues } from '../types.ts';
2
2
 
3
- export default function areElementsWithIssuesEqual(
3
+ export function areElementsWithIssuesEqual(
4
4
  elementWithIssues1: BaseElementWithIssues,
5
- elementWithIssues2: BaseElementWithIssues
5
+ elementWithIssues2: BaseElementWithIssues,
6
6
  ) {
7
- return elementWithIssues1.element === elementWithIssues2.element
8
- && elementWithIssues1.rootNode === elementWithIssues2.rootNode;
7
+ return (
8
+ elementWithIssues1.element === elementWithIssues2.element &&
9
+ elementWithIssues1.rootNode === elementWithIssues2.rootNode
10
+ );
9
11
  }
@@ -1,14 +1,14 @@
1
1
  import assert from 'node:assert/strict';
2
- import {suite, test} from 'node:test';
3
- import areIssueSetsEqual from './are-issue-sets-equal.js';
2
+ import { suite, test } from 'node:test';
4
3
  import type { Issue } from '../types';
4
+ import { areIssueSetsEqual } from './are-issue-sets-equal';
5
5
 
6
6
  const issue1: Issue = {
7
7
  id: 'id1',
8
8
  title: 'title1',
9
9
  description: 'description1',
10
10
  url: 'http://example.com',
11
- impact: 'serious'
11
+ impact: 'serious',
12
12
  };
13
13
 
14
14
  const issue2: Issue = {
@@ -16,18 +16,22 @@ const issue2: Issue = {
16
16
  title: 'title2',
17
17
  description: 'description2',
18
18
  url: 'http://example.com',
19
- impact: 'serious'
19
+ impact: 'serious',
20
20
  };
21
21
 
22
22
  // @ts-expect-error
23
- const issue2Clone: Issue = Object.keys(issue2).reduce((obj, key) => { obj[key] = issue2[key]; return obj; }, {});
23
+ const issue2Clone: Issue = Object.keys(issue2).reduce((obj, key) => {
24
+ // @ts-expect-error
25
+ obj[key] = issue2[key];
26
+ return obj;
27
+ }, {});
24
28
 
25
29
  const issue3: Issue = {
26
30
  id: 'id3',
27
31
  title: 'title3',
28
32
  description: 'description3',
29
33
  url: 'http://example.com',
30
- impact: 'serious'
34
+ impact: 'serious',
31
35
  };
32
36
 
33
37
  suite('areIssueSetsEqual', () => {
@@ -1,10 +1,12 @@
1
- import type { Issue } from '../types';
1
+ import type { Issue } from '../types.ts';
2
2
 
3
3
  const issueProps: Array<keyof Issue> = ['id', 'title', 'description', 'url', 'impact'];
4
4
 
5
- export default function areIssueSetsEqual(issues1: Array<Issue>, issues2: Array<Issue>) {
6
- return issues1.length === issues2.length &&
7
- issues1.every(issue1 => Boolean(issues2.find(issue2 =>
8
- issueProps.every(prop => issue2[prop] === issue1[prop])
9
- )));
5
+ export function areIssueSetsEqual(issues1: Array<Issue>, issues2: Array<Issue>) {
6
+ return (
7
+ issues1.length === issues2.length &&
8
+ issues1.every((issue1) =>
9
+ Boolean(issues2.find((issue2) => issueProps.every((prop) => issue2[prop] === issue1[prop]))),
10
+ )
11
+ );
10
12
  }
@@ -7,7 +7,10 @@
7
7
  *
8
8
  * It's only meant to be used during initialization.
9
9
  */
10
- function testContainingBlockCreation<T extends keyof CSSStyleDeclaration>(prop: T, value: CSSStyleDeclaration[T]) {
10
+ function testContainingBlockCreation<T extends keyof CSSStyleDeclaration>(
11
+ prop: T,
12
+ value: CSSStyleDeclaration[T],
13
+ ) {
11
14
  const container = document.createElement('div');
12
15
  container.style[prop] = value;
13
16
  container.style.position = 'fixed';
@@ -40,13 +43,13 @@ export function createsContainingBlock(prop: keyof CSSStyleDeclaration) {
40
43
 
41
44
  export function initializeContainingBlockSupportSet() {
42
45
  type StyleEntry<T extends keyof CSSStyleDeclaration> = {
43
- [K in T]: { prop: K; value: CSSStyleDeclaration[K] }
46
+ [K in T]: { prop: K; value: CSSStyleDeclaration[K] };
44
47
  }[T];
45
48
 
46
49
  const propsToTest: Array<StyleEntry<'filter' | 'backdropFilter' | 'containerType'>> = [
47
50
  { prop: 'filter', value: 'blur(1px)' },
48
51
  { prop: 'backdropFilter', value: 'blur(1px)' },
49
- { prop: 'containerType', value: 'size' }
52
+ { prop: 'containerType', value: 'size' },
50
53
  ];
51
54
 
52
55
  for (const { prop, value } of propsToTest) {
@@ -1,7 +1,7 @@
1
- import { JSDOM } from 'jsdom';
2
1
  import assert from 'node:assert/strict';
3
2
  import { suite, test } from 'node:test';
4
- import contains from './contains';
3
+ import { JSDOM } from 'jsdom';
4
+ import { contains } from './contains';
5
5
 
6
6
  suite('contains', () => {
7
7
  test('an element contains itself', () => {
@@ -1,6 +1,6 @@
1
1
  import { isDocumentFragment, isShadowRoot } from './dom-helpers.js';
2
2
 
3
- export default function contains(ancestor: Node, descendant: Node): boolean {
3
+ export function contains(ancestor: Node, descendant: Node): boolean {
4
4
  if (ancestor.contains(descendant)) {
5
5
  return true;
6
6
  }
@@ -1,3 +1,3 @@
1
1
  export function deduplicateNodes(nodes: Array<Node>): Array<Node> {
2
- return [...new Set(nodes)];;
2
+ return [...new Set(nodes)];
3
3
  }
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { suite, test } from 'node:test';
3
3
 
4
- import deepMerge from './deep-merge';
4
+ import { deepMerge } from './deep-merge';
5
5
 
6
6
  suite('deepMerge', () => {
7
7
  test('merges two objects with overlapping keys', () => {
@@ -31,4 +31,11 @@ suite('deepMerge', () => {
31
31
  const result = deepMerge(target, source);
32
32
  assert.deepEqual(result, { a: [4, 5] });
33
33
  });
34
+
35
+ test('handles merging an object into a string in a logical way', () => {
36
+ const target = { a: 'hello' };
37
+ const source = { a: { b: 'bye' } };
38
+ const result = deepMerge(target, source);
39
+ assert.deepEqual(result, { a: { b: 'bye' } });
40
+ });
34
41
  });
@@ -1,16 +1,22 @@
1
+ import { isNode } from './dom-helpers.js';
2
+
3
+ // biome-ignore lint/suspicious/noExplicitAny: I'm not sure how to type this properly
1
4
  type AnyObject = Record<string, any>;
2
5
 
3
- export default function deepMerge(target: AnyObject, source: AnyObject): AnyObject {
4
- const output = {...target};
6
+ const isObject = (obj: unknown): obj is AnyObject =>
7
+ typeof obj === 'object' && obj !== null && !Array.isArray(obj);
8
+
9
+ export function deepMerge(target: AnyObject, source: AnyObject): AnyObject {
10
+ const output = { ...target };
5
11
  for (const key of Object.keys(source)) {
6
- if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
7
- if (!(key in target)) {
8
- output[key] = source[key];
9
- } else {
12
+ if (isObject(source[key])) {
13
+ // Don't merge DOM nodes.
14
+ if (isObject(target[key]) && !isNode(target[key])) {
10
15
  output[key] = deepMerge(target[key], source[key]);
16
+ } else {
17
+ output[key] = source[key];
11
18
  }
12
- }
13
- else {
19
+ } else {
14
20
  output[key] = source[key];
15
21
  }
16
22
  }
@@ -1,6 +1,10 @@
1
1
  export function isNode(obj: object): obj is Node {
2
- return 'nodeType' in obj && typeof obj.nodeType === 'number' &&
3
- 'nodeName' in obj && typeof obj.nodeName === 'string';
2
+ return (
3
+ 'nodeType' in obj &&
4
+ typeof obj.nodeType === 'number' &&
5
+ 'nodeName' in obj &&
6
+ typeof obj.nodeName === 'string'
7
+ );
4
8
  }
5
9
 
6
10
  export function isNodeList(obj: object): obj is NodeList {
@@ -1,6 +1,6 @@
1
- export default function ensureNonEmpty<T>(arr: T[]): [T, ...T[]] {
1
+ export function ensureNonEmpty<T>(arr: T[]): [T, ...T[]] {
2
2
  if (arr.length === 0) {
3
- throw new Error("Array must not be empty");
3
+ throw new Error('Array must not be empty');
4
4
  }
5
5
  return arr as [T, ...T[]];
6
6
  }
@@ -1,4 +1,4 @@
1
- export default function getElementHtml(element: Element) {
1
+ export function getElementHtml(element: Element) {
2
2
  const outerHtml = element.outerHTML;
3
3
  const innerHtml = element.innerHTML;
4
4
  if (!innerHtml) {
@@ -9,5 +9,7 @@ export default function getElementHtml(element: Element) {
9
9
  // This shouldn't be happening, but if it does, we can just return the outer HTML.
10
10
  return outerHtml;
11
11
  }
12
- return outerHtml.slice(0, index) + '…' + outerHtml.slice(index + innerHtml.length);
12
+ const openingTag = outerHtml.slice(0, index);
13
+ const closingTag = outerHtml.slice(index + innerHtml.length);
14
+ return `${openingTag}…${closingTag}`;
13
15
  }
@@ -1,23 +1,36 @@
1
- import type { Position } from '../types';
2
- import { isHtmlElement } from './dom-helpers.js';
3
- import getParent from './get-parent.js';
1
+ import type { Position } from '../types.ts';
4
2
  import { createsContainingBlock } from './containing-blocks.js';
3
+ import { isHtmlElement } from './dom-helpers.js';
4
+ import { getParent } from './get-parent.js';
5
5
 
6
6
  // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block#identifying_the_containing_block
7
7
  function isContainingBlock(element: Element, win: Window): boolean {
8
8
  const style = win.getComputedStyle(element);
9
- const { transform, perspective, contain, contentVisibility, containerType, filter, backdropFilter, willChange } = style;
9
+ const {
10
+ transform,
11
+ perspective,
12
+ contain,
13
+ contentVisibility,
14
+ containerType,
15
+ filter,
16
+ backdropFilter,
17
+ willChange,
18
+ } = style;
10
19
  const containItems = contain.split(' ');
11
20
  const willChangeItems = willChange.split(/\s*,\s*/);
12
21
 
13
- return transform !== 'none'
14
- || perspective !== 'none'
15
- || containItems.some((item) => ['layout', 'paint', 'strict', 'content'].includes(item))
16
- || contentVisibility === 'auto'
17
- || (createsContainingBlock('containerType') && containerType !== 'normal')
18
- || (createsContainingBlock('filter') && filter !== 'none')
19
- || (createsContainingBlock('backdropFilter') && backdropFilter !== 'none')
20
- || willChangeItems.some((item) => ['transform', 'perspective', 'contain', 'filter', 'backdrop-filter'].includes(item));
22
+ return (
23
+ transform !== 'none' ||
24
+ perspective !== 'none' ||
25
+ containItems.some((item) => ['layout', 'paint', 'strict', 'content'].includes(item)) ||
26
+ contentVisibility === 'auto' ||
27
+ (createsContainingBlock('containerType') && containerType !== 'normal') ||
28
+ (createsContainingBlock('filter') && filter !== 'none') ||
29
+ (createsContainingBlock('backdropFilter') && backdropFilter !== 'none') ||
30
+ willChangeItems.some((item) =>
31
+ ['transform', 'perspective', 'contain', 'filter', 'backdrop-filter'].includes(item),
32
+ )
33
+ );
21
34
  }
22
35
 
23
36
  function getNonInitialContainingBlock(element: Element, win: Window): Element | null {
@@ -39,7 +52,7 @@ function getNonInitialContainingBlock(element: Element, win: Window): Element |
39
52
  * * The element itself, or one of the element's ancestors has a scale or rotate transform.
40
53
  * * The browser doesn't support anchor positioning.
41
54
  */
42
- export default function getElementPosition(element: Element, win: Window): Position {
55
+ export function getElementPosition(element: Element, win: Window): Position {
43
56
  const nonInitialContainingBlock = getNonInitialContainingBlock(element, win);
44
57
  // If an element has a containing block as an ancestor,
45
58
  // and that containing block is not the <html> element (the initial containing block),
@@ -60,17 +73,17 @@ export default function getElementPosition(element: Element, win: Window): Posit
60
73
  currentElement = currentElement.offsetParent as HTMLElement | null;
61
74
  }
62
75
  return { top, left, width, height };
63
- } else {
64
- const elementRect = element.getBoundingClientRect();
65
- const nonInitialContainingBlockRect = nonInitialContainingBlock.getBoundingClientRect();
66
- return {
67
- top: elementRect.top - nonInitialContainingBlockRect.top,
68
- height: elementRect.height,
69
- left: elementRect.left - nonInitialContainingBlockRect.left,
70
- width: elementRect.width
71
- };
72
76
  }
73
- } else {
74
- return element.getBoundingClientRect();
77
+
78
+ const elementRect = element.getBoundingClientRect();
79
+ const nonInitialContainingBlockRect = nonInitialContainingBlock.getBoundingClientRect();
80
+ return {
81
+ top: elementRect.top - nonInitialContainingBlockRect.top,
82
+ height: elementRect.height,
83
+ left: elementRect.left - nonInitialContainingBlockRect.left,
84
+ width: elementRect.width,
85
+ };
75
86
  }
87
+
88
+ return element.getBoundingClientRect();
76
89
  }
@@ -1,6 +1,6 @@
1
1
  import { isDocumentFragment, isShadowRoot } from './dom-helpers.js';
2
2
 
3
- export default function getParent (element: Element): Element | null {
3
+ export function getParent(element: Element): Element | null {
4
4
  if (element.parentElement) {
5
5
  return element.parentElement;
6
6
  }