accented 0.0.2 → 1.0.0
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/NOTICE +14 -0
- package/README.md +44 -187
- package/dist/accented.d.ts +8 -8
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +37 -30
- package/dist/accented.js.map +1 -1
- package/dist/common/tokens.d.ts +7 -0
- package/dist/common/tokens.d.ts.map +1 -0
- package/dist/common/tokens.js +8 -0
- package/dist/common/tokens.js.map +1 -0
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.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 +73 -31
- 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 +110 -94
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +14 -9
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +77 -22
- package/dist/elements/accented-trigger.js.map +1 -1
- package/dist/fullscreen-listener.d.ts +2 -0
- package/dist/fullscreen-listener.d.ts.map +1 -0
- package/dist/fullscreen-listener.js +17 -0
- package/dist/fullscreen-listener.js.map +1 -0
- 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 +76 -43
- 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 +3 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +5 -3
- 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 +140 -49
- 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 +3 -0
- package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -0
- package/dist/utils/are-elements-with-issues-equal.js +5 -0
- package/dist/utils/are-elements-with-issues-equal.js.map +1 -0
- 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 +8 -5
- package/dist/utils/deep-merge.js.map +1 -1
- package/dist/utils/dom-helpers.d.ts +9 -0
- package/dist/utils/dom-helpers.d.ts.map +1 -0
- package/dist/utils/dom-helpers.js +34 -0
- package/dist/utils/dom-helpers.js.map +1 -0
- 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 +64 -16
- package/dist/utils/get-element-position.js.map +1 -1
- package/dist/utils/get-parent.d.ts +2 -0
- package/dist/utils/get-parent.d.ts.map +1 -0
- package/dist/utils/get-parent.js +12 -0
- package/dist/utils/get-parent.js.map +1 -0
- 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 +10 -6
- 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 +10 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js +61 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -0
- 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 +23 -10
- 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 +56 -24
- 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 +15 -7
- package/src/accented.test.ts +2 -2
- package/src/accented.ts +45 -34
- package/src/common/tokens.ts +10 -0
- package/src/constants.ts +2 -1
- package/src/dom-updater.ts +87 -34
- package/src/elements/accented-dialog.ts +157 -122
- package/src/elements/accented-trigger.ts +119 -47
- package/src/fullscreen-listener.ts +21 -0
- 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 +113 -57
- package/src/scroll-listeners.ts +27 -19
- package/src/state.ts +27 -16
- package/src/task-queue.test.ts +5 -4
- package/src/task-queue.ts +8 -6
- package/src/types.ts +179 -76
- package/src/utils/are-elements-with-issues-equal.ts +11 -0
- 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 +54 -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 +14 -8
- package/src/utils/dom-helpers.ts +42 -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 +84 -16
- package/src/utils/get-parent.ts +14 -0
- 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 +15 -7
- 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 +75 -0
- package/src/utils/supports-anchor-positioning.ts +3 -3
- package/src/utils/transform-violations.test.ts +28 -24
- package/src/utils/transform-violations.ts +30 -12
- package/src/utils/update-elements-with-issues.test.ts +139 -51
- package/src/utils/update-elements-with-issues.ts +123 -54
- package/src/validate-options.ts +154 -14
|
@@ -1,74 +1,143 @@
|
|
|
1
|
-
import type { AxeResults } from 'axe-core';
|
|
2
1
|
import type { Signal } from '@preact/signals-core';
|
|
3
2
|
import { batch, signal } from '@preact/signals-core';
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import type {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
3
|
+
import type { AxeResults } from 'axe-core';
|
|
4
|
+
import type { AccentedDialog } from '../elements/accented-dialog.ts';
|
|
5
|
+
import type { AccentedTrigger } from '../elements/accented-trigger.ts';
|
|
6
|
+
import type { ExtendedElementWithIssues, ScanContext } from '../types.ts';
|
|
7
|
+
import { areElementsWithIssuesEqual } from './are-elements-with-issues-equal.js';
|
|
8
|
+
import { areIssueSetsEqual } from './are-issue-sets-equal.js';
|
|
9
|
+
import { isSvgElement } from './dom-helpers.js';
|
|
10
|
+
import { getElementPosition } from './get-element-position.js';
|
|
11
|
+
import { getParent } from './get-parent.js';
|
|
12
|
+
import { getScrollableAncestors } from './get-scrollable-ancestors.js';
|
|
13
|
+
import { isNodeInScanContext } from './is-node-in-scan-context.js';
|
|
14
|
+
import { supportsAnchorPositioning } from './supports-anchor-positioning.js';
|
|
15
|
+
import { transformViolations } from './transform-violations.js';
|
|
16
|
+
|
|
17
|
+
function shouldSkipRender(element: Element): boolean {
|
|
18
|
+
// Skip rendering if the element is inside an SVG:
|
|
19
|
+
// https://github.com/pomerantsev/accented/issues/62
|
|
20
|
+
const parent = getParent(element);
|
|
21
|
+
const isInsideSvg = Boolean(parent && isSvgElement(parent));
|
|
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;
|
|
31
|
+
}
|
|
12
32
|
|
|
13
33
|
let count = 0;
|
|
14
34
|
|
|
15
|
-
export
|
|
16
|
-
|
|
35
|
+
export function updateElementsWithIssues({
|
|
36
|
+
extendedElementsWithIssues,
|
|
37
|
+
scanContext,
|
|
38
|
+
violations,
|
|
39
|
+
win,
|
|
40
|
+
name,
|
|
41
|
+
}: {
|
|
42
|
+
extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>>;
|
|
43
|
+
scanContext: ScanContext;
|
|
44
|
+
violations: typeof AxeResults.violations;
|
|
45
|
+
win: Window & { CSS: typeof CSS };
|
|
46
|
+
name: string;
|
|
47
|
+
}) {
|
|
48
|
+
const updatedElementsWithIssues = transformViolations(violations, name);
|
|
17
49
|
|
|
18
50
|
batch(() => {
|
|
19
51
|
for (const updatedElementWithIssues of updatedElementsWithIssues) {
|
|
20
|
-
const existingElementIndex = extendedElementsWithIssues.value.findIndex(
|
|
21
|
-
|
|
22
|
-
|
|
52
|
+
const existingElementIndex = extendedElementsWithIssues.value.findIndex(
|
|
53
|
+
(extendedElementWithIssues) =>
|
|
54
|
+
areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues),
|
|
55
|
+
);
|
|
56
|
+
if (
|
|
57
|
+
existingElementIndex > -1 &&
|
|
58
|
+
extendedElementsWithIssues.value[existingElementIndex] &&
|
|
59
|
+
!areIssueSetsEqual(
|
|
60
|
+
extendedElementsWithIssues.value[existingElementIndex].issues.value,
|
|
61
|
+
updatedElementWithIssues.issues,
|
|
62
|
+
)
|
|
63
|
+
) {
|
|
64
|
+
extendedElementsWithIssues.value[existingElementIndex].issues.value =
|
|
65
|
+
updatedElementWithIssues.issues;
|
|
23
66
|
}
|
|
24
67
|
}
|
|
25
68
|
|
|
26
|
-
const addedElementsWithIssues = updatedElementsWithIssues.filter(updatedElementWithIssues => {
|
|
27
|
-
return !extendedElementsWithIssues.value.some(extendedElementWithIssues =>
|
|
69
|
+
const addedElementsWithIssues = updatedElementsWithIssues.filter((updatedElementWithIssues) => {
|
|
70
|
+
return !extendedElementsWithIssues.value.some((extendedElementWithIssues) =>
|
|
71
|
+
areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues),
|
|
72
|
+
);
|
|
28
73
|
});
|
|
29
74
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
75
|
+
// Only consider an element to be removed in two cases:
|
|
76
|
+
// 1. It has been removed from the DOM.
|
|
77
|
+
// 2. It is within the scan context, but not among updatedElementsWithIssues.
|
|
78
|
+
const removedElementsWithIssues = extendedElementsWithIssues.value.filter(
|
|
79
|
+
(extendedElementWithIssues) => {
|
|
80
|
+
const isConnected = extendedElementWithIssues.element.isConnected;
|
|
81
|
+
const hasNoMoreIssues =
|
|
82
|
+
isNodeInScanContext(extendedElementWithIssues.element, scanContext) &&
|
|
83
|
+
!updatedElementsWithIssues.some((updatedElementWithIssues) =>
|
|
84
|
+
areElementsWithIssuesEqual(updatedElementWithIssues, extendedElementWithIssues),
|
|
85
|
+
);
|
|
86
|
+
return !isConnected || hasNoMoreIssues;
|
|
87
|
+
},
|
|
88
|
+
);
|
|
33
89
|
|
|
34
90
|
if (addedElementsWithIssues.length > 0 || removedElementsWithIssues.length > 0) {
|
|
35
91
|
extendedElementsWithIssues.value = [...extendedElementsWithIssues.value]
|
|
36
|
-
.filter(extendedElementWithIssues => {
|
|
37
|
-
return !removedElementsWithIssues.some(removedElementWithIssues =>
|
|
92
|
+
.filter((extendedElementWithIssues) => {
|
|
93
|
+
return !removedElementsWithIssues.some((removedElementWithIssues) =>
|
|
94
|
+
areElementsWithIssuesEqual(removedElementWithIssues, extendedElementWithIssues),
|
|
95
|
+
);
|
|
38
96
|
})
|
|
39
|
-
.concat(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
.concat(
|
|
98
|
+
addedElementsWithIssues
|
|
99
|
+
.filter((addedElementWithIssues) => addedElementWithIssues.element.isConnected)
|
|
100
|
+
.map((addedElementWithIssues) => {
|
|
101
|
+
const id = count++;
|
|
102
|
+
const trigger = win.document.createElement(`${name}-trigger`) as AccentedTrigger;
|
|
103
|
+
const elementZIndex = Number.parseInt(
|
|
104
|
+
win.getComputedStyle(addedElementWithIssues.element).zIndex,
|
|
105
|
+
10,
|
|
106
|
+
);
|
|
107
|
+
if (!Number.isNaN(elementZIndex)) {
|
|
108
|
+
trigger.style.setProperty('z-index', (elementZIndex + 1).toString(), 'important');
|
|
109
|
+
}
|
|
110
|
+
trigger.style.setProperty('position-anchor', `--${name}-anchor-${id}`, 'important');
|
|
111
|
+
trigger.dataset.id = id.toString();
|
|
112
|
+
const accentedDialog = win.document.createElement(`${name}-dialog`) as AccentedDialog;
|
|
113
|
+
trigger.dialog = accentedDialog;
|
|
114
|
+
const position = getElementPosition(addedElementWithIssues.element, win);
|
|
115
|
+
trigger.position = signal(position);
|
|
116
|
+
trigger.visible = signal(true);
|
|
117
|
+
trigger.element = addedElementWithIssues.element;
|
|
118
|
+
const scrollableAncestors = supportsAnchorPositioning(win)
|
|
119
|
+
? new Set<HTMLElement>()
|
|
120
|
+
: getScrollableAncestors(addedElementWithIssues.element, win);
|
|
121
|
+
const issues = signal(addedElementWithIssues.issues);
|
|
122
|
+
accentedDialog.issues = issues;
|
|
123
|
+
accentedDialog.element = addedElementWithIssues.element;
|
|
124
|
+
return {
|
|
125
|
+
id,
|
|
126
|
+
element: addedElementWithIssues.element,
|
|
127
|
+
skipRender: shouldSkipRender(addedElementWithIssues.element),
|
|
128
|
+
rootNode: addedElementWithIssues.rootNode,
|
|
129
|
+
visible: trigger.visible,
|
|
130
|
+
position: trigger.position,
|
|
131
|
+
scrollableAncestors: signal(scrollableAncestors),
|
|
132
|
+
anchorNameValue:
|
|
133
|
+
addedElementWithIssues.element.style.getPropertyValue('anchor-name') ||
|
|
134
|
+
win
|
|
135
|
+
.getComputedStyle(addedElementWithIssues.element)
|
|
136
|
+
.getPropertyValue('anchor-name'),
|
|
137
|
+
trigger,
|
|
138
|
+
issues,
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
72
141
|
);
|
|
73
142
|
}
|
|
74
143
|
});
|
package/src/validate-options.ts
CHANGED
|
@@ -1,44 +1,184 @@
|
|
|
1
|
-
import type { AccentedOptions } from './types';
|
|
2
1
|
import { allowedAxeOptions } from './types.js';
|
|
2
|
+
import type {
|
|
3
|
+
AccentedOptions,
|
|
4
|
+
Context,
|
|
5
|
+
ContextObject,
|
|
6
|
+
ContextProp,
|
|
7
|
+
Selector,
|
|
8
|
+
SelectorList,
|
|
9
|
+
} from './types.ts';
|
|
10
|
+
import { isNode, isNodeList } from './utils/dom-helpers.js';
|
|
11
|
+
|
|
12
|
+
function isSelector(contextFragment: Context): contextFragment is Selector {
|
|
13
|
+
return (
|
|
14
|
+
typeof contextFragment === 'string' ||
|
|
15
|
+
isNode(contextFragment) ||
|
|
16
|
+
'fromShadowDom' in contextFragment
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validateSelector(selector: Selector) {
|
|
21
|
+
if (typeof selector === 'string') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (isNode(selector)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if ('fromShadowDom' in selector) {
|
|
28
|
+
if (
|
|
29
|
+
!Array.isArray(selector.fromShadowDom) ||
|
|
30
|
+
selector.fromShadowDom.length < 2 ||
|
|
31
|
+
!selector.fromShadowDom.every((item) => typeof item === 'string')
|
|
32
|
+
) {
|
|
33
|
+
throw new TypeError(
|
|
34
|
+
`Accented: invalid argument. \`fromShadowDom\` must be an array of strings with at least 2 elements. It’s currently set to ${selector.fromShadowDom}.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const neverSelector: never = selector;
|
|
40
|
+
throw new TypeError(
|
|
41
|
+
`Accented: invalid argument. The selector must be one of: string, Node, or an object with a \`fromShadowDom\` property. It’s currently set to ${neverSelector}.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isSelectorList(contextFragment: Context): contextFragment is SelectorList {
|
|
46
|
+
return (
|
|
47
|
+
(typeof contextFragment === 'object' && isNodeList(contextFragment)) ||
|
|
48
|
+
(Array.isArray(contextFragment) && contextFragment.every((item) => isSelector(item)))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validateSelectorList(selectorList: SelectorList) {
|
|
53
|
+
if (isNodeList(selectorList)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(selectorList)) {
|
|
57
|
+
for (const selector of selectorList) {
|
|
58
|
+
validateSelector(selector);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
const neverSelectorList: never = selectorList;
|
|
62
|
+
throw new TypeError(
|
|
63
|
+
`Accented: invalid argument. The selector list must either be a NodeList or an array. It’s currently set to ${neverSelectorList}.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isContextProp(contextFragment: Context): contextFragment is ContextProp {
|
|
69
|
+
return isSelector(contextFragment) || isSelectorList(contextFragment);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validateContextProp(context: Selector | SelectorList) {
|
|
73
|
+
if (isSelector(context)) {
|
|
74
|
+
validateSelector(context);
|
|
75
|
+
} else if (isSelectorList(context)) {
|
|
76
|
+
validateSelectorList(context);
|
|
77
|
+
} else {
|
|
78
|
+
const neverContext: never = context;
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`Accented: invalid argument. The context property must either be a selector or a selector list. It’s currently set to ${neverContext}.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isContextObject(contextFragment: Context): contextFragment is ContextObject {
|
|
86
|
+
return (
|
|
87
|
+
typeof contextFragment === 'object' &&
|
|
88
|
+
contextFragment !== null &&
|
|
89
|
+
('include' in contextFragment || 'exclude' in contextFragment)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateContextObject(contextObject: ContextObject) {
|
|
94
|
+
if ('include' in contextObject && contextObject.include !== undefined) {
|
|
95
|
+
validateContextProp(contextObject.include);
|
|
96
|
+
}
|
|
97
|
+
if ('exclude' in contextObject && contextObject.exclude !== undefined) {
|
|
98
|
+
validateContextProp(contextObject.exclude);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validateContext(context: Context) {
|
|
103
|
+
if (isContextProp(context)) {
|
|
104
|
+
validateContextProp(context);
|
|
105
|
+
} else if (isContextObject(context)) {
|
|
106
|
+
validateContextObject(context);
|
|
107
|
+
} else {
|
|
108
|
+
const neverContext: never = context;
|
|
109
|
+
throw new TypeError(
|
|
110
|
+
`Accented: invalid context argument. It’s currently set to ${neverContext}.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
3
114
|
|
|
4
115
|
// The space of valid CSS and HTML names is wider than this,
|
|
5
116
|
// but with Unicode it gets complicated quickly, so I'm sticking to only allowing
|
|
6
117
|
// lowercase alphanumeric names that possibly contain dashes that start with a letter.
|
|
7
118
|
const nameRegex = /^[a-z]([a-z0-9]|-)+$/;
|
|
8
119
|
|
|
9
|
-
export
|
|
120
|
+
export function validateOptions(options: AccentedOptions) {
|
|
10
121
|
if (typeof options !== 'object' || options === null) {
|
|
11
|
-
throw new TypeError(
|
|
122
|
+
throw new TypeError(
|
|
123
|
+
`Accented: invalid argument. The options parameter must be an object if provided. It’s currently set to ${options}.`,
|
|
124
|
+
);
|
|
12
125
|
}
|
|
13
126
|
if (options.throttle !== undefined) {
|
|
14
127
|
if (typeof options.throttle !== 'object' || options.throttle === null) {
|
|
15
|
-
throw new TypeError(
|
|
128
|
+
throw new TypeError(
|
|
129
|
+
`Accented: invalid argument. \`throttle\` option must be an object if provided. It’s currently set to ${options.throttle}.`,
|
|
130
|
+
);
|
|
16
131
|
}
|
|
17
|
-
if (
|
|
18
|
-
|
|
132
|
+
if (
|
|
133
|
+
options.throttle.wait !== undefined &&
|
|
134
|
+
(typeof options.throttle.wait !== 'number' || options.throttle.wait < 0)
|
|
135
|
+
) {
|
|
136
|
+
throw new TypeError(
|
|
137
|
+
`Accented: invalid argument. \`throttle.wait\` option must be a non-negative number if provided. It’s currently set to ${options.throttle.wait}.`,
|
|
138
|
+
);
|
|
19
139
|
}
|
|
20
140
|
}
|
|
21
141
|
if (options.output !== undefined) {
|
|
22
142
|
if (typeof options.output !== 'object' || options.output === null) {
|
|
23
|
-
throw new TypeError(
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
`Accented: invalid argument. \`output\` option must be an object if provided. It’s currently set to ${options.output}.`,
|
|
145
|
+
);
|
|
24
146
|
}
|
|
25
147
|
if (options.output.console !== undefined && typeof options.output.console !== 'boolean') {
|
|
26
|
-
console.warn(
|
|
148
|
+
console.warn(
|
|
149
|
+
`Accented: invalid argument. \`output.console\` option is expected to be a boolean. It’s currently set to ${options.output.console}.`,
|
|
150
|
+
);
|
|
27
151
|
}
|
|
28
152
|
}
|
|
29
153
|
if (options.callback !== undefined && typeof options.callback !== 'function') {
|
|
30
|
-
throw new TypeError(
|
|
154
|
+
throw new TypeError(
|
|
155
|
+
`Accented: invalid argument. \`callback\` option must be a function if provided. It’s currently set to ${options.callback}.`,
|
|
156
|
+
);
|
|
31
157
|
}
|
|
32
|
-
if (
|
|
33
|
-
|
|
158
|
+
if (
|
|
159
|
+
options.name !== undefined &&
|
|
160
|
+
(typeof options.name !== 'string' || !options.name.match(nameRegex))
|
|
161
|
+
) {
|
|
162
|
+
throw new TypeError(
|
|
163
|
+
`Accented: invalid argument. \`name\` option must be a string that starts with a lowercase letter and only contains lowercase alphanumeric characters and dashes. It’s currently set to ${options.name}.`,
|
|
164
|
+
);
|
|
34
165
|
}
|
|
35
166
|
if (options.axeOptions !== undefined) {
|
|
36
167
|
if (typeof options.axeOptions !== 'object' || options.axeOptions === null) {
|
|
37
|
-
throw new TypeError(
|
|
168
|
+
throw new TypeError(
|
|
169
|
+
`Accented: invalid argument. \`axeOptions\` option must be an object if provided. It’s currently set to ${options.axeOptions}.`,
|
|
170
|
+
);
|
|
38
171
|
}
|
|
39
|
-
const unsupportedKeys = Object.keys(options.axeOptions).filter(
|
|
172
|
+
const unsupportedKeys = Object.keys(options.axeOptions).filter(
|
|
173
|
+
(key) => !(allowedAxeOptions as unknown as Array<string>).includes(key),
|
|
174
|
+
);
|
|
40
175
|
if (unsupportedKeys.length > 0) {
|
|
41
|
-
throw new TypeError(
|
|
176
|
+
throw new TypeError(
|
|
177
|
+
`Accented: invalid argument. \`axeOptions\` contains the following unsupported keys: ${unsupportedKeys.join(', ')}. Valid options are: ${allowedAxeOptions.join(', ')}.`,
|
|
178
|
+
);
|
|
42
179
|
}
|
|
43
180
|
}
|
|
181
|
+
if (options.context !== undefined) {
|
|
182
|
+
validateContext(options.context);
|
|
183
|
+
}
|
|
44
184
|
}
|