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.
- package/README.md +0 -209
- package/dist/accented.d.ts +2 -2
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +24 -20
- package/dist/accented.js.map +1 -1
- package/dist/common/tokens.d.ts +2 -0
- package/dist/common/tokens.d.ts.map +1 -0
- package/dist/common/tokens.js +2 -0
- package/dist/common/tokens.js.map +1 -0
- package/dist/dom-updater.d.ts +1 -1
- package/dist/dom-updater.d.ts.map +1 -1
- package/dist/dom-updater.js +14 -13
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +2 -3
- package/dist/elements/accented-dialog.d.ts.map +1 -1
- package/dist/elements/accented-dialog.js +14 -8
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +3 -4
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +8 -10
- package/dist/elements/accented-trigger.js.map +1 -1
- package/dist/fullscreen-listener.d.ts +1 -1
- package/dist/fullscreen-listener.d.ts.map +1 -1
- package/dist/fullscreen-listener.js +3 -4
- package/dist/fullscreen-listener.js.map +1 -1
- package/dist/intersection-observer.d.ts +1 -1
- package/dist/intersection-observer.d.ts.map +1 -1
- package/dist/intersection-observer.js +12 -6
- package/dist/intersection-observer.js.map +1 -1
- package/dist/log-and-rethrow.d.ts +1 -1
- package/dist/log-and-rethrow.d.ts.map +1 -1
- package/dist/log-and-rethrow.js +2 -3
- package/dist/log-and-rethrow.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/dist/register-elements.d.ts +1 -1
- package/dist/register-elements.d.ts.map +1 -1
- package/dist/register-elements.js +6 -7
- package/dist/register-elements.js.map +1 -1
- package/dist/resize-listener.d.ts +1 -1
- package/dist/resize-listener.d.ts.map +1 -1
- package/dist/resize-listener.js +3 -4
- package/dist/resize-listener.js.map +1 -1
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +25 -27
- package/dist/scanner.js.map +1 -1
- package/dist/scroll-listeners.d.ts +1 -1
- package/dist/scroll-listeners.d.ts.map +1 -1
- package/dist/scroll-listeners.js +3 -4
- package/dist/scroll-listeners.js.map +1 -1
- package/dist/state.d.ts +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -5
- package/dist/state.js.map +1 -1
- package/dist/task-queue.d.ts +2 -2
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +1 -1
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/are-elements-with-issues-equal.d.ts +2 -2
- package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -1
- package/dist/utils/are-elements-with-issues-equal.js +3 -3
- package/dist/utils/are-elements-with-issues-equal.js.map +1 -1
- package/dist/utils/are-issue-sets-equal.d.ts +2 -2
- package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
- package/dist/utils/are-issue-sets-equal.js +3 -3
- package/dist/utils/are-issue-sets-equal.js.map +1 -1
- package/dist/utils/containing-blocks.d.ts.map +1 -1
- package/dist/utils/containing-blocks.js +1 -1
- package/dist/utils/containing-blocks.js.map +1 -1
- package/dist/utils/contains.d.ts +1 -1
- package/dist/utils/contains.d.ts.map +1 -1
- package/dist/utils/contains.js +1 -1
- package/dist/utils/contains.js.map +1 -1
- package/dist/utils/deduplicate-nodes.js +0 -1
- package/dist/utils/deduplicate-nodes.js.map +1 -1
- package/dist/utils/deep-merge.d.ts +1 -1
- package/dist/utils/deep-merge.d.ts.map +1 -1
- package/dist/utils/deep-merge.js +6 -5
- package/dist/utils/deep-merge.js.map +1 -1
- package/dist/utils/dom-helpers.d.ts.map +1 -1
- package/dist/utils/dom-helpers.js +4 -2
- package/dist/utils/dom-helpers.js.map +1 -1
- package/dist/utils/ensure-non-empty.d.ts +1 -1
- package/dist/utils/ensure-non-empty.d.ts.map +1 -1
- package/dist/utils/ensure-non-empty.js +2 -2
- package/dist/utils/ensure-non-empty.js.map +1 -1
- package/dist/utils/get-element-html.d.ts +1 -1
- package/dist/utils/get-element-html.d.ts.map +1 -1
- package/dist/utils/get-element-html.js +4 -2
- package/dist/utils/get-element-html.js.map +1 -1
- package/dist/utils/get-element-position.d.ts +2 -2
- package/dist/utils/get-element-position.d.ts.map +1 -1
- package/dist/utils/get-element-position.js +21 -25
- package/dist/utils/get-element-position.js.map +1 -1
- package/dist/utils/get-parent.d.ts +1 -1
- package/dist/utils/get-parent.d.ts.map +1 -1
- package/dist/utils/get-parent.js +1 -1
- package/dist/utils/get-parent.js.map +1 -1
- package/dist/utils/get-scan-context.d.ts +2 -2
- package/dist/utils/get-scan-context.d.ts.map +1 -1
- package/dist/utils/get-scan-context.js +9 -9
- package/dist/utils/get-scan-context.js.map +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/get-scrollable-ancestors.js +5 -5
- package/dist/utils/get-scrollable-ancestors.js.map +1 -1
- package/dist/utils/is-node-in-scan-context.d.ts +2 -2
- package/dist/utils/is-node-in-scan-context.d.ts.map +1 -1
- package/dist/utils/is-node-in-scan-context.js +5 -5
- package/dist/utils/is-node-in-scan-context.js.map +1 -1
- package/dist/utils/is-non-empty.d.ts +2 -0
- package/dist/utils/is-non-empty.d.ts.map +1 -0
- package/dist/utils/is-non-empty.js +4 -0
- package/dist/utils/is-non-empty.js.map +1 -0
- package/dist/utils/normalize-context.d.ts +2 -2
- package/dist/utils/normalize-context.d.ts.map +1 -1
- package/dist/utils/normalize-context.js +10 -8
- package/dist/utils/normalize-context.js.map +1 -1
- package/dist/utils/recalculate-positions.d.ts +1 -1
- package/dist/utils/recalculate-positions.d.ts.map +1 -1
- package/dist/utils/recalculate-positions.js +5 -5
- package/dist/utils/recalculate-positions.js.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
- package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.js +19 -22
- package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -1
- package/dist/utils/supports-anchor-positioning.d.ts +1 -1
- package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
- package/dist/utils/supports-anchor-positioning.js +1 -1
- package/dist/utils/supports-anchor-positioning.js.map +1 -1
- package/dist/utils/transform-violations.d.ts +2 -2
- package/dist/utils/transform-violations.d.ts.map +1 -1
- package/dist/utils/transform-violations.js +9 -9
- package/dist/utils/transform-violations.js.map +1 -1
- package/dist/utils/update-elements-with-issues.d.ts +3 -3
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +34 -29
- package/dist/utils/update-elements-with-issues.js.map +1 -1
- package/dist/validate-options.d.ts +2 -2
- package/dist/validate-options.d.ts.map +1 -1
- package/dist/validate-options.js +24 -23
- package/dist/validate-options.js.map +1 -1
- package/package.json +5 -3
- package/src/accented.test.ts +2 -2
- package/src/accented.ts +34 -26
- package/src/common/tokens.ts +1 -0
- package/src/dom-updater.ts +26 -19
- package/src/elements/accented-dialog.ts +69 -43
- package/src/elements/accented-trigger.ts +52 -43
- package/src/fullscreen-listener.ts +15 -11
- package/src/intersection-observer.ts +27 -16
- package/src/log-and-rethrow.ts +2 -3
- package/src/logger.ts +8 -6
- package/src/register-elements.ts +7 -7
- package/src/resize-listener.ts +15 -11
- package/src/scanner.ts +55 -41
- package/src/scroll-listeners.ts +27 -19
- package/src/state.ts +24 -21
- package/src/task-queue.test.ts +5 -4
- package/src/task-queue.ts +2 -2
- package/src/types.ts +52 -53
- package/src/utils/are-elements-with-issues-equal.ts +7 -5
- package/src/utils/are-issue-sets-equal.test.ts +10 -6
- package/src/utils/are-issue-sets-equal.ts +8 -6
- package/src/utils/containing-blocks.ts +6 -3
- package/src/utils/contains.test.ts +2 -2
- package/src/utils/contains.ts +1 -1
- package/src/utils/deduplicate-nodes.ts +1 -1
- package/src/utils/deep-merge.test.ts +8 -1
- package/src/utils/deep-merge.ts +11 -8
- package/src/utils/dom-helpers.ts +6 -2
- package/src/utils/ensure-non-empty.ts +2 -2
- package/src/utils/get-element-html.ts +4 -2
- package/src/utils/get-element-position.ts +37 -24
- package/src/utils/get-parent.ts +1 -1
- package/src/utils/get-scan-context.test.ts +14 -8
- package/src/utils/get-scan-context.ts +12 -15
- package/src/utils/get-scrollable-ancestors.ts +8 -5
- package/src/utils/is-node-in-scan-context.test.ts +3 -3
- package/src/utils/is-node-in-scan-context.ts +6 -6
- package/src/utils/is-non-empty.ts +3 -0
- package/src/utils/normalize-context.test.ts +9 -9
- package/src/utils/normalize-context.ts +17 -10
- package/src/utils/recalculate-positions.ts +5 -5
- package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
- package/src/utils/shadow-dom-aware-mutation-observer.ts +21 -24
- package/src/utils/supports-anchor-positioning.ts +3 -3
- package/src/utils/transform-violations.test.ts +22 -20
- package/src/utils/transform-violations.ts +14 -10
- package/src/utils/update-elements-with-issues.test.ts +49 -49
- package/src/utils/update-elements-with-issues.ts +96 -71
- 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
|
|
6
|
-
import
|
|
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
|
|
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(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
}
|
|
174
|
-
|
|
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
|
|
4
|
+
export function setupResizeListener() {
|
|
5
5
|
const abortController = new AbortController();
|
|
6
|
-
window.addEventListener(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
6
|
-
const intersectionObserver = new IntersectionObserver(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
}
|
|
18
|
-
|
|
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
|
}
|
package/src/log-and-rethrow.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { issuesUrl } from './constants.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
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
|
|
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
|
|
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(
|
|
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) {
|
package/src/register-elements.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { getAccentedDialog } from './elements/accented-dialog.js';
|
|
2
|
+
import { getAccentedTrigger } from './elements/accented-trigger.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
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
|
+
}
|
package/src/resize-listener.ts
CHANGED
|
@@ -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
|
|
4
|
+
export function setupResizeListener() {
|
|
5
5
|
const abortController = new AbortController();
|
|
6
|
-
window.addEventListener(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
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 =
|
|
106
|
-
|
|
107
|
-
[...mutationRecord.
|
|
108
|
-
|
|
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(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 () => {
|
package/src/scroll-listeners.ts
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
import { effect } from '@preact/signals-core';
|
|
2
|
-
import
|
|
2
|
+
import { logAndRethrow } from './log-and-rethrow.js';
|
|
3
3
|
import { scrollableAncestors } from './state.js';
|
|
4
|
-
import
|
|
4
|
+
import { recalculatePositions } from './utils/recalculate-positions.js';
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export function setupScrollListeners() {
|
|
7
7
|
const documentAbortController = new AbortController();
|
|
8
|
-
document.addEventListener(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 {
|
|
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>>(() =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
(
|
|
18
|
-
|
|
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
|
-
(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
);
|