accented 0.0.0-20250404114312 → 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 -207
- package/dist/accented.d.ts +2 -2
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +29 -23
- 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/constants.d.ts.map +1 -1
- package/dist/dom-updater.d.ts +1 -1
- package/dist/dom-updater.d.ts.map +1 -1
- package/dist/dom-updater.js +43 -16
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +13 -10
- package/dist/elements/accented-dialog.d.ts.map +1 -1
- package/dist/elements/accented-dialog.js +46 -69
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +12 -9
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +14 -15
- 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 +6 -3
- 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 +39 -36
- 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 +4 -4
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +3 -2
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +28 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/are-elements-with-issues-equal.d.ts +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 +3 -0
- package/dist/utils/containing-blocks.d.ts.map +1 -0
- package/dist/utils/containing-blocks.js +46 -0
- package/dist/utils/containing-blocks.js.map +1 -0
- package/dist/utils/contains.d.ts +2 -0
- package/dist/utils/contains.d.ts.map +1 -0
- package/dist/utils/contains.js +19 -0
- package/dist/utils/contains.js.map +1 -0
- package/dist/utils/deduplicate-nodes.d.ts +2 -0
- package/dist/utils/deduplicate-nodes.d.ts.map +1 -0
- package/dist/utils/deduplicate-nodes.js +4 -0
- package/dist/utils/deduplicate-nodes.js.map +1 -0
- 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 +3 -0
- package/dist/utils/dom-helpers.d.ts.map +1 -1
- package/dist/utils/dom-helpers.js +15 -0
- package/dist/utils/dom-helpers.js.map +1 -1
- package/dist/utils/ensure-non-empty.d.ts +2 -0
- package/dist/utils/ensure-non-empty.d.ts.map +1 -0
- package/dist/utils/ensure-non-empty.js +7 -0
- package/dist/utils/ensure-non-empty.js.map +1 -0
- package/dist/utils/get-element-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 +10 -2
- package/dist/utils/get-element-position.d.ts.map +1 -1
- package/dist/utils/get-element-position.js +34 -23
- 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 +3 -0
- package/dist/utils/get-scan-context.d.ts.map +1 -0
- package/dist/utils/get-scan-context.js +28 -0
- package/dist/utils/get-scan-context.js.map +1 -0
- package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/get-scrollable-ancestors.js +5 -5
- package/dist/utils/get-scrollable-ancestors.js.map +1 -1
- package/dist/utils/is-node-in-scan-context.d.ts +3 -0
- package/dist/utils/is-node-in-scan-context.d.ts.map +1 -0
- package/dist/utils/is-node-in-scan-context.js +26 -0
- package/dist/utils/is-node-in-scan-context.js.map +1 -0
- package/dist/utils/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 +3 -0
- package/dist/utils/normalize-context.d.ts.map +1 -0
- package/dist/utils/normalize-context.js +59 -0
- package/dist/utils/normalize-context.js.map +1 -0
- 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 +11 -5
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +54 -26
- 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 +91 -4
- package/dist/validate-options.js.map +1 -1
- package/package.json +11 -5
- package/src/accented.test.ts +2 -2
- package/src/accented.ts +38 -28
- package/src/common/tokens.ts +1 -0
- package/src/dom-updater.ts +59 -22
- package/src/elements/accented-dialog.ts +102 -106
- package/src/elements/accented-trigger.ts +58 -48
- 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 +14 -4
- package/src/register-elements.ts +7 -7
- package/src/resize-listener.ts +15 -11
- package/src/scanner.ts +70 -50
- 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 +8 -6
- package/src/types.ts +74 -42
- 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 +60 -0
- package/src/utils/contains.test.ts +55 -0
- package/src/utils/contains.ts +19 -0
- package/src/utils/deduplicate-nodes.ts +3 -0
- package/src/utils/deep-merge.test.ts +8 -1
- package/src/utils/deep-merge.ts +11 -8
- package/src/utils/dom-helpers.ts +20 -0
- package/src/utils/ensure-non-empty.ts +6 -0
- package/src/utils/get-element-html.ts +4 -2
- package/src/utils/get-element-position.ts +51 -22
- package/src/utils/get-parent.ts +1 -1
- package/src/utils/get-scan-context.test.ts +85 -0
- package/src/utils/get-scan-context.ts +36 -0
- package/src/utils/get-scrollable-ancestors.ts +8 -5
- package/src/utils/is-node-in-scan-context.test.ts +70 -0
- package/src/utils/is-node-in-scan-context.ts +29 -0
- package/src/utils/is-non-empty.ts +3 -0
- package/src/utils/normalize-context.test.ts +105 -0
- package/src/utils/normalize-context.ts +65 -0
- 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 +102 -49
- package/src/utils/update-elements-with-issues.ts +122 -58
- package/src/validate-options.ts +154 -14
|
@@ -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
|
|
|
@@ -44,11 +42,12 @@ export default (name: string) => {
|
|
|
44
42
|
#trigger {
|
|
45
43
|
pointer-events: auto;
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
margin-inline-start: auto;
|
|
46
|
+
margin-inline-end: 4px;
|
|
47
|
+
margin-block-start: 4px;
|
|
50
48
|
|
|
51
49
|
box-sizing: border-box;
|
|
50
|
+
font-family: system-ui;
|
|
52
51
|
font-size: calc(var(--ratio) * var(--ratio) * var(--base-size));
|
|
53
52
|
inline-size: calc(2 * var(--base-size));
|
|
54
53
|
block-size: calc(2 * var(--base-size));
|
|
@@ -81,7 +80,7 @@ export default (name: string) => {
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
</style>
|
|
84
|
-
<button id="trigger" lang="en"
|
|
83
|
+
<button id="trigger" lang="en">á</button>
|
|
85
84
|
`;
|
|
86
85
|
|
|
87
86
|
return class extends HTMLElement implements AccentedTrigger {
|
|
@@ -104,8 +103,8 @@ export default (name: string) => {
|
|
|
104
103
|
visible: Signal<boolean> | undefined;
|
|
105
104
|
|
|
106
105
|
constructor() {
|
|
106
|
+
super();
|
|
107
107
|
try {
|
|
108
|
-
super();
|
|
109
108
|
this.attachShadow({ mode: 'open' });
|
|
110
109
|
const content = template.content.cloneNode(true);
|
|
111
110
|
if (this.shadowRoot) {
|
|
@@ -137,42 +136,50 @@ export default (name: string) => {
|
|
|
137
136
|
|
|
138
137
|
if (this.element) {
|
|
139
138
|
this.#elementMutationObserver.observe(this.element, {
|
|
140
|
-
attributes: true
|
|
139
|
+
attributes: true,
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
this.#abortController = new AbortController();
|
|
145
|
-
trigger?.addEventListener(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
171
179
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}, { signal: this.#abortController.signal });
|
|
180
|
+
},
|
|
181
|
+
{ signal: this.#abortController.signal },
|
|
182
|
+
);
|
|
176
183
|
|
|
177
184
|
if (!supportsAnchorPositioning(window)) {
|
|
178
185
|
this.#disposeOfPositionEffect = effect(() => {
|
|
@@ -184,11 +191,14 @@ export default (name: string) => {
|
|
|
184
191
|
this.style.setProperty('height', `${position.height}px`, 'important');
|
|
185
192
|
}
|
|
186
193
|
});
|
|
187
|
-
|
|
188
|
-
this.#disposeOfVisibilityEffect = effect(() => {
|
|
189
|
-
this.style.setProperty('visibility', this.visible?.value ? 'visible' : 'hidden', 'important');
|
|
190
|
-
});
|
|
191
194
|
}
|
|
195
|
+
this.#disposeOfVisibilityEffect = effect(() => {
|
|
196
|
+
this.style.setProperty(
|
|
197
|
+
'visibility',
|
|
198
|
+
this.visible?.value ? 'visible' : 'hidden',
|
|
199
|
+
'important',
|
|
200
|
+
);
|
|
201
|
+
});
|
|
192
202
|
}
|
|
193
203
|
} catch (error) {
|
|
194
204
|
logAndRethrow(error);
|
|
@@ -200,7 +210,7 @@ export default (name: string) => {
|
|
|
200
210
|
if (this.#abortController) {
|
|
201
211
|
this.#abortController.abort();
|
|
202
212
|
}
|
|
203
|
-
if (this.#dialogCloseAbortController) {
|
|
213
|
+
if (this.#dialogCloseAbortController && !this.dialog?.open) {
|
|
204
214
|
this.#dialogCloseAbortController.abort();
|
|
205
215
|
this.dialog?.remove();
|
|
206
216
|
}
|
|
@@ -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,9 +1,13 @@
|
|
|
1
1
|
import { effect } from '@preact/signals-core';
|
|
2
|
-
import { elementsWithIssues, enabled } from './state.js';
|
|
3
2
|
import { accentedUrl } from './constants.js';
|
|
3
|
+
import { elementsWithIssues, enabled } from './state.js';
|
|
4
|
+
import type { ElementWithIssues } from './types.ts';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
function filterPropsForOutput(elements: Array<ElementWithIssues>) {
|
|
7
|
+
return elements.map(({ element, issues }) => ({ element, issues }));
|
|
8
|
+
}
|
|
6
9
|
|
|
10
|
+
export function createLogger() {
|
|
7
11
|
let firstRun = true;
|
|
8
12
|
|
|
9
13
|
return effect(() => {
|
|
@@ -13,8 +17,14 @@ export default function createLogger() {
|
|
|
13
17
|
|
|
14
18
|
const elementCount = elementsWithIssues.value.length;
|
|
15
19
|
if (elementCount > 0) {
|
|
16
|
-
const issueCount = elementsWithIssues.value.reduce(
|
|
17
|
-
|
|
20
|
+
const issueCount = elementsWithIssues.value.reduce(
|
|
21
|
+
(acc, { issues }) => acc + issues.length,
|
|
22
|
+
0,
|
|
23
|
+
);
|
|
24
|
+
console.log(
|
|
25
|
+
`${issueCount} accessibility issue${issueCount === 1 ? '' : 's'} found in ${elementCount} element${issueCount === 1 ? '' : 's'} (Accented, ${accentedUrl}):\n`,
|
|
26
|
+
filterPropsForOutput(elementsWithIssues.value),
|
|
27
|
+
);
|
|
18
28
|
} else {
|
|
19
29
|
if (firstRun) {
|
|
20
30
|
firstRun = false;
|
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,26 @@
|
|
|
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, AxeContext } 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
|
-
|
|
13
|
-
|
|
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
|
+
) {
|
|
14
21
|
const axeRunningWindowProp = `__${name}_axe_running__`;
|
|
15
|
-
const win
|
|
16
|
-
const taskQueue = new TaskQueue<Node>(async () => {
|
|
22
|
+
const win = window as unknown as Record<string, boolean>;
|
|
23
|
+
const taskQueue = new TaskQueue<Node>(async (nodes) => {
|
|
17
24
|
// We may see errors coming from axe-core when Accented is toggled off and on in qiuck succession,
|
|
18
25
|
// which I've seen happen with hot reloading of a React application.
|
|
19
26
|
// This window property serves as a circuit breaker for that particular case.
|
|
@@ -22,17 +29,16 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
|
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
try {
|
|
25
|
-
|
|
26
32
|
performance.mark('scan-start');
|
|
27
33
|
|
|
28
34
|
win[axeRunningWindowProp] = true;
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
const scanContext = getScanContext(nodes, context);
|
|
37
|
+
|
|
38
|
+
let result: axe.AxeResults | undefined;
|
|
31
39
|
|
|
32
40
|
try {
|
|
33
|
-
|
|
34
|
-
// only run Axe on what's changed, not on the whole axeContext
|
|
35
|
-
result = await axe.run(axeContext, {
|
|
41
|
+
result = await axe.run(scanContext, {
|
|
36
42
|
elementRef: true,
|
|
37
43
|
// Although axe-core can perform iframe scanning, I haven't succeeded in it,
|
|
38
44
|
// and the docs suggest that the axe-core script should be explicitly included
|
|
@@ -42,29 +48,32 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
|
|
|
42
48
|
// A consumer of Accented can instead scan the iframed document by calling Accented initialization from that document.
|
|
43
49
|
iframes: false,
|
|
44
50
|
resultTypes: ['violations'],
|
|
45
|
-
...axeOptions
|
|
51
|
+
...axeOptions,
|
|
46
52
|
});
|
|
47
53
|
} catch (error) {
|
|
48
54
|
console.error(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`If you still think it’s a bug in Accented, file an issue at ${issuesUrl}.\n`,
|
|
52
|
-
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,
|
|
53
57
|
);
|
|
54
|
-
result = { violations: [] };
|
|
55
58
|
}
|
|
56
59
|
win[axeRunningWindowProp] = false;
|
|
57
60
|
|
|
58
61
|
const scanMeasure = performance.measure('scan', 'scan-start');
|
|
59
62
|
const scanDuration = Math.round(scanMeasure.duration);
|
|
60
63
|
|
|
61
|
-
if (!enabled.value) {
|
|
64
|
+
if (!enabled.value || !result) {
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
performance.mark('dom-update-start');
|
|
66
69
|
|
|
67
|
-
updateElementsWithIssues(
|
|
70
|
+
updateElementsWithIssues({
|
|
71
|
+
extendedElementsWithIssues,
|
|
72
|
+
scanContext,
|
|
73
|
+
violations: result.violations,
|
|
74
|
+
win: window,
|
|
75
|
+
name,
|
|
76
|
+
});
|
|
68
77
|
|
|
69
78
|
const domUpdateMeasure = performance.measure('dom-update', 'dom-update-start');
|
|
70
79
|
const domUpdateDuration = Math.round(domUpdateMeasure.duration);
|
|
@@ -74,8 +83,12 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
|
|
|
74
83
|
performance: {
|
|
75
84
|
totalBlockingTime: scanDuration + domUpdateDuration,
|
|
76
85
|
scan: scanDuration,
|
|
77
|
-
domUpdate: domUpdateDuration
|
|
78
|
-
|
|
86
|
+
domUpdate: domUpdateDuration,
|
|
87
|
+
// Assuming that the {include, exclude} shape of the context object will be used less often
|
|
88
|
+
// than other variants, we'll output just the `include` array in case nothing is excluded
|
|
89
|
+
// in the scan.
|
|
90
|
+
scanContext: scanContext.exclude.length > 0 ? scanContext : scanContext.include,
|
|
91
|
+
},
|
|
79
92
|
});
|
|
80
93
|
} catch (error) {
|
|
81
94
|
win[axeRunningWindowProp] = false;
|
|
@@ -83,21 +96,24 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
|
|
|
83
96
|
}
|
|
84
97
|
}, throttle);
|
|
85
98
|
|
|
86
|
-
// TODO (https://github.com/pomerantsev/accented/issues/102):
|
|
87
|
-
// limit to what's in axeContext,
|
|
88
|
-
// if that's an element or array of elements (not a selector).
|
|
89
99
|
taskQueue.add(document);
|
|
90
100
|
|
|
91
101
|
const accentedElementNames = getAccentedElementNames(name);
|
|
92
|
-
const mutationObserver = createShadowDOMAwareMutationObserver(name, mutationList => {
|
|
102
|
+
const mutationObserver = createShadowDOMAwareMutationObserver(name, (mutationList) => {
|
|
93
103
|
try {
|
|
94
104
|
// We're not interested in mutations that are caused exclusively by the custom elements
|
|
95
105
|
// introduced by Accented.
|
|
96
|
-
const listWithoutAccentedElements = mutationList.filter(mutationRecord => {
|
|
97
|
-
const onlyAccentedElementsAddedOrRemoved =
|
|
98
|
-
|
|
99
|
-
[...mutationRecord.
|
|
100
|
-
|
|
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' &&
|
|
101
117
|
accentedElementNames.includes(mutationRecord.target.nodeName.toLowerCase());
|
|
102
118
|
return !(onlyAccentedElementsAddedOrRemoved || accentedElementChanged);
|
|
103
119
|
});
|
|
@@ -117,31 +133,35 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
|
|
|
117
133
|
// If we simply exclude all mutations where attributeName = `data-${name}`,
|
|
118
134
|
// we may miss other mutations on those same elements caused by Accented,
|
|
119
135
|
// leading to extra runs of the mutation observer.
|
|
120
|
-
const elementsWithAccentedAttributeChanges = listWithoutAccentedElements.reduce(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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) => {
|
|
128
150
|
return !elementsWithAccentedAttributeChanges.has(mutationRecord.target);
|
|
129
151
|
});
|
|
130
152
|
|
|
131
|
-
|
|
153
|
+
const nodes = filteredMutationList.map((mutationRecord) => mutationRecord.target);
|
|
154
|
+
taskQueue.addMultiple(nodes);
|
|
132
155
|
} catch (error) {
|
|
133
156
|
logAndRethrow(error);
|
|
134
157
|
}
|
|
135
158
|
});
|
|
136
159
|
|
|
137
|
-
// TODO (https://github.com/pomerantsev/accented/issues/102):
|
|
138
|
-
// possibly limit the observer to what's in axeContext,
|
|
139
|
-
// if that's an element or array of elements (not a selector).
|
|
140
160
|
mutationObserver.observe(document, {
|
|
141
161
|
subtree: true,
|
|
142
162
|
childList: true,
|
|
143
163
|
attributes: true,
|
|
144
|
-
characterData: true
|
|
164
|
+
characterData: true,
|
|
145
165
|
});
|
|
146
166
|
|
|
147
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
|
+
}
|