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
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
|
);
|
package/src/task-queue.test.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import {mock, suite, test} from 'node:test';
|
|
2
|
+
import { mock, suite, test } from 'node:test';
|
|
3
3
|
|
|
4
|
-
import TaskQueue from './task-queue
|
|
4
|
+
import { TaskQueue } from './task-queue';
|
|
5
5
|
|
|
6
|
-
const wait = (duration: number) => new Promise(resolve => setTimeout(resolve, duration));
|
|
6
|
+
const wait = (duration: number) => new Promise((resolve) => setTimeout(resolve, duration));
|
|
7
7
|
|
|
8
|
-
const createAsyncCallback = (duration: number) =>
|
|
8
|
+
const createAsyncCallback = (duration: number) =>
|
|
9
|
+
mock.fn(() => new Promise((resolve) => setTimeout(resolve, duration)));
|
|
9
10
|
|
|
10
11
|
suite('TaskQueue', () => {
|
|
11
12
|
test('callback is not called after a TaskQueue is created, even after a timeout', async () => {
|
package/src/task-queue.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { Throttle } from './types';
|
|
1
|
+
import type { Throttle } from './types.ts';
|
|
2
2
|
|
|
3
|
-
type TaskCallback = () => void;
|
|
3
|
+
type TaskCallback<T> = (items: Array<T>) => void;
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export class TaskQueue<T> {
|
|
6
6
|
#throttle: Throttle;
|
|
7
|
-
#asyncCallback: TaskCallback | null = null;
|
|
7
|
+
#asyncCallback: TaskCallback<T> | null = null;
|
|
8
8
|
|
|
9
9
|
#items = new Set<T>();
|
|
10
10
|
#inRunLoop = false;
|
|
11
11
|
|
|
12
|
-
constructor(asyncCallback: TaskCallback
|
|
12
|
+
constructor(asyncCallback: TaskCallback<T>, throttle: Required<Throttle>) {
|
|
13
13
|
this.#asyncCallback = asyncCallback;
|
|
14
14
|
this.#throttle = throttle;
|
|
15
15
|
}
|
|
@@ -33,10 +33,12 @@ export default class TaskQueue<T> {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const items = Array.from(this.#items);
|
|
37
|
+
|
|
36
38
|
this.#items.clear();
|
|
37
39
|
|
|
38
40
|
if (this.#asyncCallback) {
|
|
39
|
-
await this.#asyncCallback();
|
|
41
|
+
await this.#asyncCallback(items);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
await new Promise((resolve) => setTimeout(resolve, this.#throttle.wait));
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type axe from 'axe-core';
|
|
2
1
|
import type { Signal } from '@preact/signals-core';
|
|
3
|
-
import type
|
|
2
|
+
import type axe from 'axe-core';
|
|
3
|
+
import type { AccentedTrigger } from './elements/accented-trigger.ts';
|
|
4
4
|
|
|
5
5
|
export type Throttle = {
|
|
6
6
|
/**
|
|
@@ -8,7 +8,7 @@ export type Throttle = {
|
|
|
8
8
|
*
|
|
9
9
|
* Default: `1000`.
|
|
10
10
|
* */
|
|
11
|
-
wait?: number
|
|
11
|
+
wait?: number;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* When to run the scan on Accented initialization or on a mutation.
|
|
@@ -17,8 +17,8 @@ export type Throttle = {
|
|
|
17
17
|
*
|
|
18
18
|
* Default: `true`.
|
|
19
19
|
* */
|
|
20
|
-
leading?: boolean
|
|
21
|
-
}
|
|
20
|
+
leading?: boolean;
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
export type Output = {
|
|
24
24
|
/**
|
|
@@ -26,20 +26,44 @@ export type Output = {
|
|
|
26
26
|
*
|
|
27
27
|
* Default: `true`.
|
|
28
28
|
* */
|
|
29
|
-
console?: boolean
|
|
30
|
-
}
|
|
29
|
+
console?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Model context type based on axe.ElementContext,
|
|
34
|
+
* excluding frame selectors (since we don't support scanning iframes).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export type Selector = Exclude<axe.Selector, axe.LabelledFramesSelector>;
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
// axe.SelectorList also can have FrameSelector elements in the array.
|
|
40
|
+
// We're not allowing that.
|
|
41
|
+
export type SelectorList = Array<Selector> | NodeList;
|
|
42
|
+
|
|
43
|
+
// The rest of the type is structured the same as in axe-core.
|
|
44
|
+
export type ContextProp = Selector | SelectorList;
|
|
45
|
+
|
|
46
|
+
export type ContextObject =
|
|
47
|
+
| {
|
|
48
|
+
include: ContextProp;
|
|
49
|
+
exclude?: ContextProp;
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
exclude: ContextProp;
|
|
53
|
+
include?: ContextProp;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type Context = ContextProp | ContextObject;
|
|
33
57
|
|
|
34
58
|
export const allowedAxeOptions = ['rules', 'runOnly'] as const;
|
|
35
59
|
|
|
36
|
-
export type AxeOptions = Pick<axe.RunOptions, typeof allowedAxeOptions[number]>;
|
|
60
|
+
export type AxeOptions = Pick<axe.RunOptions, (typeof allowedAxeOptions)[number]>;
|
|
37
61
|
|
|
38
62
|
type CallbackParams = {
|
|
39
63
|
/**
|
|
40
64
|
* The most current array of elements with issues.
|
|
41
65
|
* */
|
|
42
|
-
elementsWithIssues: Array<ElementWithIssues
|
|
66
|
+
elementsWithIssues: Array<ElementWithIssues>;
|
|
43
67
|
|
|
44
68
|
/**
|
|
45
69
|
* * `performance`: runtime performance of the last scan. An object:
|
|
@@ -47,18 +71,20 @@ type CallbackParams = {
|
|
|
47
71
|
* It’s further divided into the `scan` and `domUpdate` phases.
|
|
48
72
|
* * `scan`: how long the `scan` phase took, in milliseconds.
|
|
49
73
|
* * `domUpdate`: how long the `domUpdate` phase took, in milliseconds.
|
|
74
|
+
* * `scanContext`: nodes that got scanned. Either an array of nodes,
|
|
75
|
+
* or an object with `include` and `exclude` properties (if any nodes were excluded).
|
|
50
76
|
* */
|
|
51
77
|
performance: {
|
|
52
|
-
totalBlockingTime: number
|
|
53
|
-
scan: number
|
|
54
|
-
domUpdate: number
|
|
55
|
-
|
|
56
|
-
}
|
|
78
|
+
totalBlockingTime: number;
|
|
79
|
+
scan: number;
|
|
80
|
+
domUpdate: number;
|
|
81
|
+
scanContext: ScanContext | Array<Node>;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
57
84
|
|
|
58
85
|
export type Callback = (params: CallbackParams) => void;
|
|
59
86
|
|
|
60
87
|
export type AccentedOptions = {
|
|
61
|
-
|
|
62
88
|
/**
|
|
63
89
|
* The `context` parameter for `axe.run()`.
|
|
64
90
|
*
|
|
@@ -75,7 +101,7 @@ export type AccentedOptions = {
|
|
|
75
101
|
*
|
|
76
102
|
* Default: `document`.
|
|
77
103
|
*/
|
|
78
|
-
|
|
104
|
+
context?: Context;
|
|
79
105
|
|
|
80
106
|
/**
|
|
81
107
|
* The `options` parameter for `axe.run()`.
|
|
@@ -91,7 +117,7 @@ export type AccentedOptions = {
|
|
|
91
117
|
*
|
|
92
118
|
* Default: `{}`.
|
|
93
119
|
*/
|
|
94
|
-
axeOptions?: AxeOptions
|
|
120
|
+
axeOptions?: AxeOptions;
|
|
95
121
|
|
|
96
122
|
/**
|
|
97
123
|
* The character sequence that’s used in various elements, attributes and stylesheets that Accented adds to the page.
|
|
@@ -108,24 +134,24 @@ export type AccentedOptions = {
|
|
|
108
134
|
*
|
|
109
135
|
* Default: `accented`.
|
|
110
136
|
*/
|
|
111
|
-
name?: string
|
|
137
|
+
name?: string;
|
|
112
138
|
|
|
113
139
|
/**
|
|
114
140
|
* Output options object.
|
|
115
141
|
* */
|
|
116
|
-
output?: Output
|
|
142
|
+
output?: Output;
|
|
117
143
|
|
|
118
144
|
/**
|
|
119
145
|
* Scan throttling options object.
|
|
120
146
|
* */
|
|
121
|
-
throttle?: Throttle
|
|
147
|
+
throttle?: Throttle;
|
|
122
148
|
|
|
123
149
|
/**
|
|
124
150
|
* A callback that will be called after each scan.
|
|
125
151
|
*
|
|
126
152
|
* Default: `() => {}`.
|
|
127
153
|
* */
|
|
128
|
-
callback?: Callback
|
|
154
|
+
callback?: Callback;
|
|
129
155
|
};
|
|
130
156
|
|
|
131
157
|
/**
|
|
@@ -135,35 +161,41 @@ export type AccentedOptions = {
|
|
|
135
161
|
export type DisableAccented = () => void;
|
|
136
162
|
|
|
137
163
|
export type Position = {
|
|
138
|
-
left: number
|
|
139
|
-
top: number
|
|
140
|
-
width: number
|
|
141
|
-
height: number
|
|
164
|
+
left: number;
|
|
165
|
+
top: number;
|
|
166
|
+
width: number;
|
|
167
|
+
height: number;
|
|
142
168
|
};
|
|
143
169
|
|
|
144
170
|
export type Issue = {
|
|
145
|
-
id: string
|
|
146
|
-
title: string
|
|
147
|
-
description: string
|
|
148
|
-
url: string
|
|
149
|
-
impact: axe.ImpactValue
|
|
171
|
+
id: string;
|
|
172
|
+
title: string;
|
|
173
|
+
description: string;
|
|
174
|
+
url: string;
|
|
175
|
+
impact: axe.ImpactValue;
|
|
150
176
|
};
|
|
151
177
|
|
|
152
178
|
export type BaseElementWithIssues = {
|
|
153
|
-
element: HTMLElement
|
|
154
|
-
rootNode: Node
|
|
179
|
+
element: HTMLElement | SVGElement;
|
|
180
|
+
rootNode: Node;
|
|
155
181
|
};
|
|
156
182
|
|
|
157
|
-
export type ElementWithIssues = BaseElementWithIssues &{
|
|
158
|
-
issues: Array<Issue
|
|
183
|
+
export type ElementWithIssues = BaseElementWithIssues & {
|
|
184
|
+
issues: Array<Issue>;
|
|
159
185
|
};
|
|
160
186
|
|
|
161
187
|
export type ExtendedElementWithIssues = BaseElementWithIssues & {
|
|
162
|
-
issues: Signal<ElementWithIssues['issues']
|
|
163
|
-
visible: Signal<boolean
|
|
164
|
-
trigger: AccentedTrigger
|
|
165
|
-
position: Signal<Position
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
issues: Signal<ElementWithIssues['issues']>;
|
|
189
|
+
visible: Signal<boolean>;
|
|
190
|
+
trigger: AccentedTrigger;
|
|
191
|
+
position: Signal<Position>;
|
|
192
|
+
skipRender: boolean;
|
|
193
|
+
anchorNameValue: string;
|
|
194
|
+
scrollableAncestors: Signal<Set<Element>>;
|
|
195
|
+
id: number;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export type ScanContext = {
|
|
199
|
+
include: Array<Node>;
|
|
200
|
+
exclude: Array<Node>;
|
|
169
201
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { BaseElementWithIssues } from
|
|
1
|
+
import type { BaseElementWithIssues } from '../types.ts';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function areElementsWithIssuesEqual(
|
|
4
4
|
elementWithIssues1: BaseElementWithIssues,
|
|
5
|
-
elementWithIssues2: BaseElementWithIssues
|
|
5
|
+
elementWithIssues2: BaseElementWithIssues,
|
|
6
6
|
) {
|
|
7
|
-
return
|
|
8
|
-
|
|
7
|
+
return (
|
|
8
|
+
elementWithIssues1.element === elementWithIssues2.element &&
|
|
9
|
+
elementWithIssues1.rootNode === elementWithIssues2.rootNode
|
|
10
|
+
);
|
|
9
11
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import {suite, test} from 'node:test';
|
|
3
|
-
import areIssueSetsEqual from './are-issue-sets-equal.js';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
4
3
|
import type { Issue } from '../types';
|
|
4
|
+
import { areIssueSetsEqual } from './are-issue-sets-equal';
|
|
5
5
|
|
|
6
6
|
const issue1: Issue = {
|
|
7
7
|
id: 'id1',
|
|
8
8
|
title: 'title1',
|
|
9
9
|
description: 'description1',
|
|
10
10
|
url: 'http://example.com',
|
|
11
|
-
impact: 'serious'
|
|
11
|
+
impact: 'serious',
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
const issue2: Issue = {
|
|
@@ -16,18 +16,22 @@ const issue2: Issue = {
|
|
|
16
16
|
title: 'title2',
|
|
17
17
|
description: 'description2',
|
|
18
18
|
url: 'http://example.com',
|
|
19
|
-
impact: 'serious'
|
|
19
|
+
impact: 'serious',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
// @ts-expect-error
|
|
23
|
-
const issue2Clone: Issue = Object.keys(issue2).reduce((obj, key) => {
|
|
23
|
+
const issue2Clone: Issue = Object.keys(issue2).reduce((obj, key) => {
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
obj[key] = issue2[key];
|
|
26
|
+
return obj;
|
|
27
|
+
}, {});
|
|
24
28
|
|
|
25
29
|
const issue3: Issue = {
|
|
26
30
|
id: 'id3',
|
|
27
31
|
title: 'title3',
|
|
28
32
|
description: 'description3',
|
|
29
33
|
url: 'http://example.com',
|
|
30
|
-
impact: 'serious'
|
|
34
|
+
impact: 'serious',
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
suite('areIssueSetsEqual', () => {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { Issue } from '../types';
|
|
1
|
+
import type { Issue } from '../types.ts';
|
|
2
2
|
|
|
3
3
|
const issueProps: Array<keyof Issue> = ['id', 'title', 'description', 'url', 'impact'];
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
return
|
|
7
|
-
issues1.
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
export function areIssueSetsEqual(issues1: Array<Issue>, issues2: Array<Issue>) {
|
|
6
|
+
return (
|
|
7
|
+
issues1.length === issues2.length &&
|
|
8
|
+
issues1.every((issue1) =>
|
|
9
|
+
Boolean(issues2.find((issue2) => issueProps.every((prop) => issue2[prop] === issue1[prop]))),
|
|
10
|
+
)
|
|
11
|
+
);
|
|
10
12
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests whether a particular combination of CSS property and value on an element
|
|
3
|
+
* makes that element a containing block.
|
|
4
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block
|
|
5
|
+
*
|
|
6
|
+
* The function is meant to be run with properties that behave inconsistently across browsers.
|
|
7
|
+
*
|
|
8
|
+
* It's only meant to be used during initialization.
|
|
9
|
+
*/
|
|
10
|
+
function testContainingBlockCreation<T extends keyof CSSStyleDeclaration>(
|
|
11
|
+
prop: T,
|
|
12
|
+
value: CSSStyleDeclaration[T],
|
|
13
|
+
) {
|
|
14
|
+
const container = document.createElement('div');
|
|
15
|
+
container.style[prop] = value;
|
|
16
|
+
container.style.position = 'fixed';
|
|
17
|
+
container.style.insetInlineStart = '10px';
|
|
18
|
+
container.style.insetBlockStart = '10px';
|
|
19
|
+
|
|
20
|
+
const element = document.createElement('div');
|
|
21
|
+
element.style.position = 'fixed';
|
|
22
|
+
element.style.insetInlineStart = '0';
|
|
23
|
+
element.style.insetBlockStart = '0';
|
|
24
|
+
|
|
25
|
+
container.appendChild(element);
|
|
26
|
+
document.body.appendChild(container);
|
|
27
|
+
const containerRect = container.getBoundingClientRect();
|
|
28
|
+
const elementRect = element.getBoundingClientRect();
|
|
29
|
+
|
|
30
|
+
container.remove();
|
|
31
|
+
|
|
32
|
+
return containerRect.top === elementRect.top && containerRect.left === elementRect.left;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// This is the set we'll use to store the properties that _may_ create containing blocks
|
|
36
|
+
// (the behavior of the ones that we'll be checking is inconsistent across browsers
|
|
37
|
+
// at the time of writing this comment).
|
|
38
|
+
const propsAffectingContainingBlocks = new Set<keyof CSSStyleDeclaration>();
|
|
39
|
+
|
|
40
|
+
export function createsContainingBlock(prop: keyof CSSStyleDeclaration) {
|
|
41
|
+
return propsAffectingContainingBlocks.has(prop);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function initializeContainingBlockSupportSet() {
|
|
45
|
+
type StyleEntry<T extends keyof CSSStyleDeclaration> = {
|
|
46
|
+
[K in T]: { prop: K; value: CSSStyleDeclaration[K] };
|
|
47
|
+
}[T];
|
|
48
|
+
|
|
49
|
+
const propsToTest: Array<StyleEntry<'filter' | 'backdropFilter' | 'containerType'>> = [
|
|
50
|
+
{ prop: 'filter', value: 'blur(1px)' },
|
|
51
|
+
{ prop: 'backdropFilter', value: 'blur(1px)' },
|
|
52
|
+
{ prop: 'containerType', value: 'size' },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const { prop, value } of propsToTest) {
|
|
56
|
+
if (testContainingBlockCreation(prop, value)) {
|
|
57
|
+
propsAffectingContainingBlocks.add(prop);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
4
|
+
import { contains } from './contains';
|
|
5
|
+
|
|
6
|
+
suite('contains', () => {
|
|
7
|
+
test('an element contains itself', () => {
|
|
8
|
+
const dom = new JSDOM('<div id="test"></div>');
|
|
9
|
+
const { document } = dom.window;
|
|
10
|
+
const element = document.querySelector('#test')!;
|
|
11
|
+
|
|
12
|
+
assert.equal(contains(element, element), true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('an element does not contain its sibling', () => {
|
|
16
|
+
const dom = new JSDOM('<div><div id="sibling1"></div><div id="sibling2"></div></div>');
|
|
17
|
+
const { document } = dom.window;
|
|
18
|
+
const sibling1 = document.querySelector('#sibling1')!;
|
|
19
|
+
const sibling2 = document.querySelector('#sibling2')!;
|
|
20
|
+
|
|
21
|
+
assert.equal(contains(sibling1, sibling2), false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('an element contain its descendant', () => {
|
|
25
|
+
const dom = new JSDOM('<div id="ancestor"><div id="descendant"></div></div>');
|
|
26
|
+
const { document } = dom.window;
|
|
27
|
+
const ancestor = document.querySelector('#ancestor')!;
|
|
28
|
+
const descendant = document.querySelector('#descendant')!;
|
|
29
|
+
|
|
30
|
+
assert.equal(contains(ancestor, descendant), true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('an element contain its descendant', () => {
|
|
34
|
+
const dom = new JSDOM('<div id="ancestor"><div id="descendant"></div></div>');
|
|
35
|
+
const { document } = dom.window;
|
|
36
|
+
const ancestor = document.querySelector('#ancestor')!;
|
|
37
|
+
const descendant = document.querySelector('#descendant')!;
|
|
38
|
+
|
|
39
|
+
assert.equal(contains(descendant, ancestor), false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('an element contain its descendant if the descendant is in a shadow DOM', () => {
|
|
43
|
+
const dom = new JSDOM('<div id="ancestor"><div id="host"></div></div>');
|
|
44
|
+
global.Node = dom.window.Node;
|
|
45
|
+
const { document } = dom.window;
|
|
46
|
+
const ancestor = document.querySelector('#ancestor')!;
|
|
47
|
+
const host = document.querySelector('#host')!;
|
|
48
|
+
const shadowRoot = host.attachShadow({ mode: 'open' });
|
|
49
|
+
shadowRoot.innerHTML = '<div id="descendant"></div>';
|
|
50
|
+
const descendant = shadowRoot.querySelector('#descendant')!;
|
|
51
|
+
console.log(descendant);
|
|
52
|
+
|
|
53
|
+
assert.equal(contains(ancestor, descendant), true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isDocumentFragment, isShadowRoot } from './dom-helpers.js';
|
|
2
|
+
|
|
3
|
+
export function contains(ancestor: Node, descendant: Node): boolean {
|
|
4
|
+
if (ancestor.contains(descendant)) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
let rootNode = descendant.getRootNode();
|
|
8
|
+
while (rootNode) {
|
|
9
|
+
if (!(isDocumentFragment(rootNode) && isShadowRoot(rootNode))) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const host = rootNode.host;
|
|
13
|
+
if (ancestor.contains(host)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
rootNode = host.getRootNode();
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
|
|
4
|
-
import deepMerge from './deep-merge';
|
|
4
|
+
import { deepMerge } from './deep-merge';
|
|
5
5
|
|
|
6
6
|
suite('deepMerge', () => {
|
|
7
7
|
test('merges two objects with overlapping keys', () => {
|
|
@@ -31,4 +31,11 @@ suite('deepMerge', () => {
|
|
|
31
31
|
const result = deepMerge(target, source);
|
|
32
32
|
assert.deepEqual(result, { a: [4, 5] });
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
test('handles merging an object into a string in a logical way', () => {
|
|
36
|
+
const target = { a: 'hello' };
|
|
37
|
+
const source = { a: { b: 'bye' } };
|
|
38
|
+
const result = deepMerge(target, source);
|
|
39
|
+
assert.deepEqual(result, { a: { b: 'bye' } });
|
|
40
|
+
});
|
|
34
41
|
});
|
package/src/utils/deep-merge.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
// biome-ignore lint/suspicious/noExplicitAny: I'm not sure how to type this properly
|
|
1
2
|
type AnyObject = Record<string, any>;
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
const isObject = (obj: unknown): obj is AnyObject =>
|
|
5
|
+
typeof obj === 'object' && obj !== null && !Array.isArray(obj);
|
|
6
|
+
|
|
7
|
+
export function deepMerge(target: AnyObject, source: AnyObject): AnyObject {
|
|
8
|
+
const output = { ...target };
|
|
5
9
|
for (const key of Object.keys(source)) {
|
|
6
|
-
if (
|
|
7
|
-
if (
|
|
8
|
-
output[key] = source[key];
|
|
9
|
-
} else {
|
|
10
|
+
if (isObject(source[key])) {
|
|
11
|
+
if (isObject(target[key])) {
|
|
10
12
|
output[key] = deepMerge(target[key], source[key]);
|
|
13
|
+
} else {
|
|
14
|
+
output[key] = source[key];
|
|
11
15
|
}
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
16
|
+
} else {
|
|
14
17
|
output[key] = source[key];
|
|
15
18
|
}
|
|
16
19
|
}
|
package/src/utils/dom-helpers.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
export function isNode(obj: object): obj is Node {
|
|
2
|
+
return (
|
|
3
|
+
'nodeType' in obj &&
|
|
4
|
+
typeof obj.nodeType === 'number' &&
|
|
5
|
+
'nodeName' in obj &&
|
|
6
|
+
typeof obj.nodeName === 'string'
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isNodeList(obj: object): obj is NodeList {
|
|
11
|
+
return Object.prototype.toString.call(obj) === '[object NodeList]';
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
export function isElement(node: Node): node is Element {
|
|
2
15
|
return typeof Node !== 'undefined' && node.nodeType === Node.ELEMENT_NODE;
|
|
3
16
|
}
|
|
@@ -20,3 +33,10 @@ export function isHtmlElement(element: Element): element is HTMLElement {
|
|
|
20
33
|
// This heuristic seems to be the most robust and fastest that I could think of.
|
|
21
34
|
return element.constructor.name.startsWith('HTML');
|
|
22
35
|
}
|
|
36
|
+
|
|
37
|
+
export function isSvgElement(element: Element): element is SVGElement {
|
|
38
|
+
// We can't use instanceof because it may not work across contexts
|
|
39
|
+
// (such as when an element is moved from an iframe).
|
|
40
|
+
// This heuristic seems to be the most robust and fastest that I could think of.
|
|
41
|
+
return element.constructor.name.startsWith('SVG');
|
|
42
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function getElementHtml(element: Element) {
|
|
2
2
|
const outerHtml = element.outerHTML;
|
|
3
3
|
const innerHtml = element.innerHTML;
|
|
4
4
|
if (!innerHtml) {
|
|
@@ -9,5 +9,7 @@ export default function getElementHtml(element: Element) {
|
|
|
9
9
|
// This shouldn't be happening, but if it does, we can just return the outer HTML.
|
|
10
10
|
return outerHtml;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const openingTag = outerHtml.slice(0, index);
|
|
13
|
+
const closingTag = outerHtml.slice(index + innerHtml.length);
|
|
14
|
+
return `${openingTag}…${closingTag}`;
|
|
13
15
|
}
|