accented 1.2.6 → 1.3.1
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/dist/constants.d.ts +12 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +32 -0
- package/dist/constants.js.map +1 -1
- package/dist/elements/accented-trigger.js +1 -1
- package/dist/elements/accented-trigger.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +79 -41
- package/dist/scanner.js.map +1 -1
- package/dist/utils/create-extended-element-with-issues.d.ts +3 -0
- package/dist/utils/create-extended-element-with-issues.d.ts.map +1 -0
- package/dist/utils/create-extended-element-with-issues.js +56 -0
- package/dist/utils/create-extended-element-with-issues.js.map +1 -0
- package/dist/utils/get-all-rules-from-axe-options.d.ts +3 -0
- package/dist/utils/get-all-rules-from-axe-options.d.ts.map +1 -0
- package/dist/utils/get-all-rules-from-axe-options.js +51 -0
- package/dist/utils/get-all-rules-from-axe-options.js.map +1 -0
- package/dist/utils/transform-violations.d.ts.map +1 -1
- package/dist/utils/transform-violations.js +2 -13
- package/dist/utils/transform-violations.js.map +1 -1
- package/dist/utils/update-elements-with-issues.d.ts +4 -3
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +36 -80
- package/dist/utils/update-elements-with-issues.js.map +1 -1
- package/package.json +5 -5
- package/src/constants.ts +34 -0
- package/src/elements/accented-trigger.ts +1 -1
- package/src/scanner.ts +91 -45
- package/src/utils/create-extended-element-with-issues.ts +67 -0
- package/src/utils/get-all-rules-from-axe-options.test.ts +169 -0
- package/src/utils/get-all-rules-from-axe-options.ts +54 -0
- package/src/utils/transform-violations.ts +2 -14
- package/src/utils/update-elements-with-issues.test.ts +223 -139
- package/src/utils/update-elements-with-issues.ts +76 -107
|
@@ -1,139 +1,108 @@
|
|
|
1
1
|
import type { Signal } from '@preact/signals-core';
|
|
2
|
-
import { batch
|
|
2
|
+
import { batch } from '@preact/signals-core';
|
|
3
3
|
import type { AxeResults } from 'axe-core';
|
|
4
|
-
import
|
|
5
|
-
import type {
|
|
6
|
-
|
|
4
|
+
import { descendantDependentRules } from '../constants.js';
|
|
5
|
+
import type {
|
|
6
|
+
BaseElementWithIssues,
|
|
7
|
+
ElementWithIssues,
|
|
8
|
+
ExtendedElementWithIssues,
|
|
9
|
+
Issue,
|
|
10
|
+
ScanContext,
|
|
11
|
+
} from '../types.ts';
|
|
7
12
|
import { areElementsWithIssuesEqual } from './are-elements-with-issues-equal.js';
|
|
8
13
|
import { areIssueSetsEqual } from './are-issue-sets-equal.js';
|
|
9
|
-
import {
|
|
10
|
-
import { getElementPosition } from './get-element-position.js';
|
|
11
|
-
import { getParent } from './get-parent.js';
|
|
12
|
-
import { getScrollableAncestors } from './get-scrollable-ancestors.js';
|
|
14
|
+
import { createExtendedElementWithIssues } from './create-extended-element-with-issues.js';
|
|
13
15
|
import { isNodeInScanContext } from './is-node-in-scan-context.js';
|
|
14
|
-
import { supportsAnchorPositioning } from './supports-anchor-positioning.js';
|
|
15
16
|
import { transformViolations } from './transform-violations.js';
|
|
16
17
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Some issues, such as meta-viewport, are on <head> descendants,
|
|
24
|
-
// but since <head> is never rendered, we don't want to output anything
|
|
25
|
-
// for those in the DOM.
|
|
26
|
-
// We're not anticipating the use of shadow DOM in <head>,
|
|
27
|
-
// so the use of .closest() should be fine.
|
|
28
|
-
const isInsideHead = element.closest('head') !== null;
|
|
29
|
-
|
|
30
|
-
return isInsideSvg || isInsideHead;
|
|
18
|
+
function getIssuesForElement(
|
|
19
|
+
element: BaseElementWithIssues,
|
|
20
|
+
list: Array<ElementWithIssues>,
|
|
21
|
+
): Array<Issue> {
|
|
22
|
+
return list.find((entry) => areElementsWithIssuesEqual(entry, element))?.issues ?? [];
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
function mergeLimitedContextAndFullContextViolations(
|
|
26
|
+
elementsFromLimitedContext: Array<ElementWithIssues>,
|
|
27
|
+
elementsFromFullContext: Array<ElementWithIssues>,
|
|
28
|
+
): Array<ElementWithIssues> {
|
|
29
|
+
const fromLimitedWithFullIssuesMerged = elementsFromLimitedContext.map((limited) => {
|
|
30
|
+
const fullMatch = elementsFromFullContext.find((full) =>
|
|
31
|
+
areElementsWithIssuesEqual(full, limited),
|
|
32
|
+
);
|
|
33
|
+
return fullMatch ? { ...limited, issues: [...limited.issues, ...fullMatch.issues] } : limited;
|
|
34
|
+
});
|
|
35
|
+
const onlyInFullContext = elementsFromFullContext.filter(
|
|
36
|
+
(full) =>
|
|
37
|
+
!elementsFromLimitedContext.some((limited) => areElementsWithIssuesEqual(limited, full)),
|
|
38
|
+
);
|
|
39
|
+
return [...fromLimitedWithFullIssuesMerged, ...onlyInFullContext];
|
|
40
|
+
}
|
|
34
41
|
|
|
35
42
|
export function updateElementsWithIssues({
|
|
36
43
|
extendedElementsWithIssues,
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
limitedContext,
|
|
45
|
+
limitedContextViolations,
|
|
46
|
+
fullContextViolations,
|
|
39
47
|
name,
|
|
40
48
|
}: {
|
|
41
49
|
extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>>;
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
limitedContext: ScanContext;
|
|
51
|
+
limitedContextViolations: AxeResults['violations'];
|
|
52
|
+
fullContextViolations: AxeResults['violations'];
|
|
44
53
|
name: string;
|
|
45
54
|
}) {
|
|
46
|
-
const
|
|
55
|
+
const updatedElementsFromLimitedContext = transformViolations(limitedContextViolations, name);
|
|
56
|
+
const updatedElementsFromFullContext = transformViolations(fullContextViolations, name);
|
|
57
|
+
|
|
58
|
+
const allUpdatedElements = mergeLimitedContextAndFullContextViolations(
|
|
59
|
+
updatedElementsFromLimitedContext,
|
|
60
|
+
updatedElementsFromFullContext,
|
|
61
|
+
);
|
|
47
62
|
|
|
48
63
|
batch(() => {
|
|
49
|
-
for (const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
for (const existing of extendedElementsWithIssues.value) {
|
|
65
|
+
// If the element is inside the limited context, axe just rescanned
|
|
66
|
+
// it — replace its issues with whatever was reported. If it's outside, keep its
|
|
67
|
+
// existing issues, except descendant-dependent ones, which may have changed due
|
|
68
|
+
// to mutations elsewhere; those get repopulated from the full-context scan below.
|
|
69
|
+
const newLimitedContextIssues = isNodeInScanContext(existing.element, limitedContext)
|
|
70
|
+
? getIssuesForElement(existing, updatedElementsFromLimitedContext)
|
|
71
|
+
: existing.issues.value.filter((issue) => !descendantDependentRules.has(issue.id));
|
|
72
|
+
|
|
73
|
+
const newFullContextIssues = getIssuesForElement(existing, updatedElementsFromFullContext);
|
|
74
|
+
|
|
75
|
+
const newIssues = [...newLimitedContextIssues, ...newFullContextIssues];
|
|
76
|
+
|
|
77
|
+
if (!areIssueSetsEqual(existing.issues.value, newIssues)) {
|
|
78
|
+
existing.issues.value = newIssues;
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
const addedElementsWithIssues =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
const addedElementsWithIssues = allUpdatedElements.filter(
|
|
83
|
+
(updated) =>
|
|
84
|
+
updated.element.isConnected &&
|
|
85
|
+
!extendedElementsWithIssues.value.some((existing) =>
|
|
86
|
+
areElementsWithIssuesEqual(existing, updated),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
72
89
|
|
|
73
|
-
// Only consider an element to be removed in two cases:
|
|
74
|
-
// 1. It has been removed from the DOM.
|
|
75
|
-
// 2. It is within the scan context, but not among updatedElementsWithIssues.
|
|
76
90
|
const removedElementsWithIssues = extendedElementsWithIssues.value.filter(
|
|
77
|
-
(
|
|
78
|
-
const isConnected = extendedElementWithIssues.element.isConnected;
|
|
79
|
-
const hasNoMoreIssues =
|
|
80
|
-
isNodeInScanContext(extendedElementWithIssues.element, scanContext) &&
|
|
81
|
-
!updatedElementsWithIssues.some((updatedElementWithIssues) =>
|
|
82
|
-
areElementsWithIssuesEqual(updatedElementWithIssues, extendedElementWithIssues),
|
|
83
|
-
);
|
|
84
|
-
return !isConnected || hasNoMoreIssues;
|
|
85
|
-
},
|
|
91
|
+
(existing) => !existing.element.isConnected || existing.issues.value.length === 0,
|
|
86
92
|
);
|
|
87
93
|
|
|
94
|
+
// Only rebuild the outer signal when set membership changes; per-element issue
|
|
95
|
+
// updates were already made in the loop above.
|
|
88
96
|
if (addedElementsWithIssues.length > 0 || removedElementsWithIssues.length > 0) {
|
|
89
97
|
extendedElementsWithIssues.value = [...extendedElementsWithIssues.value]
|
|
90
|
-
.filter(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
.filter(
|
|
99
|
+
(existing) =>
|
|
100
|
+
!removedElementsWithIssues.some((removed) =>
|
|
101
|
+
areElementsWithIssuesEqual(removed, existing),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
95
104
|
.concat(
|
|
96
|
-
addedElementsWithIssues
|
|
97
|
-
.filter((addedElementWithIssues) => addedElementWithIssues.element.isConnected)
|
|
98
|
-
.map((addedElementWithIssues) => {
|
|
99
|
-
const id = count++;
|
|
100
|
-
const trigger = document.createElement(`${name}-trigger`) as AccentedTrigger;
|
|
101
|
-
const elementZIndex = Number.parseInt(
|
|
102
|
-
getComputedStyle(addedElementWithIssues.element).zIndex,
|
|
103
|
-
10,
|
|
104
|
-
);
|
|
105
|
-
if (!Number.isNaN(elementZIndex)) {
|
|
106
|
-
trigger.style.setProperty('z-index', (elementZIndex + 1).toString(), 'important');
|
|
107
|
-
}
|
|
108
|
-
trigger.style.setProperty('position-anchor', `--${name}-anchor-${id}`, 'important');
|
|
109
|
-
trigger.dataset.id = id.toString();
|
|
110
|
-
const accentedDialog = document.createElement(`${name}-dialog`) as AccentedDialog;
|
|
111
|
-
trigger.dialog = accentedDialog;
|
|
112
|
-
const position = getElementPosition(addedElementWithIssues.element);
|
|
113
|
-
trigger.position = signal(position);
|
|
114
|
-
trigger.visible = signal(true);
|
|
115
|
-
trigger.element = addedElementWithIssues.element;
|
|
116
|
-
const scrollableAncestors = supportsAnchorPositioning()
|
|
117
|
-
? new Set<HTMLElement>()
|
|
118
|
-
: getScrollableAncestors(addedElementWithIssues.element);
|
|
119
|
-
const issues = signal(addedElementWithIssues.issues);
|
|
120
|
-
accentedDialog.issues = issues;
|
|
121
|
-
accentedDialog.element = addedElementWithIssues.element;
|
|
122
|
-
return {
|
|
123
|
-
id,
|
|
124
|
-
element: addedElementWithIssues.element,
|
|
125
|
-
skipRender: shouldSkipRender(addedElementWithIssues.element),
|
|
126
|
-
rootNode: addedElementWithIssues.rootNode,
|
|
127
|
-
visible: trigger.visible,
|
|
128
|
-
position: trigger.position,
|
|
129
|
-
scrollableAncestors: signal(scrollableAncestors),
|
|
130
|
-
anchorNameValue:
|
|
131
|
-
addedElementWithIssues.element.style.getPropertyValue('anchor-name') ||
|
|
132
|
-
getComputedStyle(addedElementWithIssues.element).getPropertyValue('anchor-name'),
|
|
133
|
-
trigger,
|
|
134
|
-
issues,
|
|
135
|
-
};
|
|
136
|
-
}),
|
|
105
|
+
addedElementsWithIssues.map((added) => createExtendedElementWithIssues(added, name)),
|
|
137
106
|
);
|
|
138
107
|
}
|
|
139
108
|
});
|