accented 0.0.0-20250424114613 → 0.0.0-20250701143712
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 +44 -193
- package/dist/accented.d.ts +7 -7
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +30 -27
- 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 +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -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 +14 -13
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +2 -3
- package/dist/elements/accented-dialog.d.ts.map +1 -1
- package/dist/elements/accented-dialog.js +14 -8
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +3 -4
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +10 -10
- package/dist/elements/accented-trigger.js.map +1 -1
- package/dist/fullscreen-listener.d.ts +1 -1
- package/dist/fullscreen-listener.d.ts.map +1 -1
- package/dist/fullscreen-listener.js +3 -4
- package/dist/fullscreen-listener.js.map +1 -1
- package/dist/intersection-observer.d.ts +1 -1
- package/dist/intersection-observer.d.ts.map +1 -1
- package/dist/intersection-observer.js +12 -6
- package/dist/intersection-observer.js.map +1 -1
- package/dist/log-and-rethrow.d.ts +1 -1
- package/dist/log-and-rethrow.d.ts.map +1 -1
- package/dist/log-and-rethrow.js +2 -3
- package/dist/log-and-rethrow.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/dist/register-elements.d.ts +1 -1
- package/dist/register-elements.d.ts.map +1 -1
- package/dist/register-elements.js +6 -7
- package/dist/register-elements.js.map +1 -1
- package/dist/resize-listener.d.ts +1 -1
- package/dist/resize-listener.d.ts.map +1 -1
- package/dist/resize-listener.js +3 -4
- package/dist/resize-listener.js.map +1 -1
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +36 -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 +2 -2
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +1 -1
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +102 -46
- 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.map +1 -1
- package/dist/utils/containing-blocks.js +1 -1
- package/dist/utils/containing-blocks.js.map +1 -1
- package/dist/utils/contains.d.ts +1 -1
- package/dist/utils/contains.d.ts.map +1 -1
- package/dist/utils/contains.js +1 -1
- package/dist/utils/contains.js.map +1 -1
- package/dist/utils/deduplicate-nodes.js +0 -1
- package/dist/utils/deduplicate-nodes.js.map +1 -1
- package/dist/utils/deep-merge.d.ts +1 -1
- package/dist/utils/deep-merge.d.ts.map +1 -1
- package/dist/utils/deep-merge.js +8 -5
- package/dist/utils/deep-merge.js.map +1 -1
- package/dist/utils/dom-helpers.d.ts.map +1 -1
- package/dist/utils/dom-helpers.js +4 -2
- package/dist/utils/dom-helpers.js.map +1 -1
- package/dist/utils/ensure-non-empty.d.ts +1 -1
- package/dist/utils/ensure-non-empty.d.ts.map +1 -1
- package/dist/utils/ensure-non-empty.js +2 -2
- package/dist/utils/ensure-non-empty.js.map +1 -1
- package/dist/utils/get-element-html.d.ts +1 -1
- package/dist/utils/get-element-html.d.ts.map +1 -1
- package/dist/utils/get-element-html.js +4 -2
- package/dist/utils/get-element-html.js.map +1 -1
- package/dist/utils/get-element-position.d.ts +2 -2
- package/dist/utils/get-element-position.d.ts.map +1 -1
- package/dist/utils/get-element-position.js +21 -25
- package/dist/utils/get-element-position.js.map +1 -1
- package/dist/utils/get-parent.d.ts +1 -1
- package/dist/utils/get-parent.d.ts.map +1 -1
- package/dist/utils/get-parent.js +1 -1
- package/dist/utils/get-parent.js.map +1 -1
- package/dist/utils/get-scan-context.d.ts +2 -2
- package/dist/utils/get-scan-context.d.ts.map +1 -1
- package/dist/utils/get-scan-context.js +9 -9
- package/dist/utils/get-scan-context.js.map +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/get-scrollable-ancestors.js +5 -5
- package/dist/utils/get-scrollable-ancestors.js.map +1 -1
- package/dist/utils/is-node-in-scan-context.d.ts +2 -2
- package/dist/utils/is-node-in-scan-context.d.ts.map +1 -1
- package/dist/utils/is-node-in-scan-context.js +5 -5
- package/dist/utils/is-node-in-scan-context.js.map +1 -1
- package/dist/utils/is-non-empty.d.ts +2 -0
- package/dist/utils/is-non-empty.d.ts.map +1 -0
- package/dist/utils/is-non-empty.js +4 -0
- package/dist/utils/is-non-empty.js.map +1 -0
- package/dist/utils/normalize-context.d.ts +2 -2
- package/dist/utils/normalize-context.d.ts.map +1 -1
- package/dist/utils/normalize-context.js +10 -8
- package/dist/utils/normalize-context.js.map +1 -1
- package/dist/utils/recalculate-positions.d.ts +1 -1
- package/dist/utils/recalculate-positions.d.ts.map +1 -1
- package/dist/utils/recalculate-positions.js +5 -5
- package/dist/utils/recalculate-positions.js.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
- package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
- package/dist/utils/shadow-dom-aware-mutation-observer.js +19 -22
- package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -1
- package/dist/utils/supports-anchor-positioning.d.ts +1 -1
- package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
- package/dist/utils/supports-anchor-positioning.js +1 -1
- package/dist/utils/supports-anchor-positioning.js.map +1 -1
- package/dist/utils/transform-violations.d.ts +2 -2
- package/dist/utils/transform-violations.d.ts.map +1 -1
- package/dist/utils/transform-violations.js +9 -9
- package/dist/utils/transform-violations.js.map +1 -1
- package/dist/utils/update-elements-with-issues.d.ts +3 -3
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +34 -29
- package/dist/utils/update-elements-with-issues.js.map +1 -1
- package/dist/validate-options.d.ts +2 -2
- package/dist/validate-options.d.ts.map +1 -1
- package/dist/validate-options.js +24 -23
- package/dist/validate-options.js.map +1 -1
- package/package.json +7 -4
- package/src/accented.test.ts +2 -2
- package/src/accented.ts +39 -32
- package/src/common/tokens.ts +1 -0
- package/src/constants.ts +1 -1
- package/src/dom-updater.ts +26 -19
- package/src/elements/accented-dialog.ts +69 -43
- package/src/elements/accented-trigger.ts +54 -43
- package/src/fullscreen-listener.ts +15 -11
- package/src/intersection-observer.ts +27 -16
- package/src/log-and-rethrow.ts +2 -3
- package/src/logger.ts +8 -6
- package/src/register-elements.ts +7 -7
- package/src/resize-listener.ts +15 -11
- package/src/scanner.ts +66 -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 +2 -2
- package/src/types.ts +151 -95
- package/src/utils/are-elements-with-issues-equal.ts +7 -5
- package/src/utils/are-issue-sets-equal.test.ts +10 -6
- package/src/utils/are-issue-sets-equal.ts +8 -6
- package/src/utils/containing-blocks.ts +6 -3
- package/src/utils/contains.test.ts +2 -2
- package/src/utils/contains.ts +1 -1
- package/src/utils/deduplicate-nodes.ts +1 -1
- package/src/utils/deep-merge.test.ts +8 -1
- package/src/utils/deep-merge.ts +14 -8
- package/src/utils/dom-helpers.ts +6 -2
- package/src/utils/ensure-non-empty.ts +2 -2
- package/src/utils/get-element-html.ts +4 -2
- package/src/utils/get-element-position.ts +37 -24
- package/src/utils/get-parent.ts +1 -1
- package/src/utils/get-scan-context.test.ts +14 -8
- package/src/utils/get-scan-context.ts +12 -15
- package/src/utils/get-scrollable-ancestors.ts +8 -5
- package/src/utils/is-node-in-scan-context.test.ts +3 -3
- package/src/utils/is-node-in-scan-context.ts +6 -6
- package/src/utils/is-non-empty.ts +3 -0
- package/src/utils/normalize-context.test.ts +9 -9
- package/src/utils/normalize-context.ts +17 -10
- package/src/utils/recalculate-positions.ts +5 -5
- package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
- package/src/utils/shadow-dom-aware-mutation-observer.ts +21 -24
- package/src/utils/supports-anchor-positioning.ts +3 -3
- package/src/utils/transform-violations.test.ts +22 -20
- package/src/utils/transform-violations.ts +14 -10
- package/src/utils/update-elements-with-issues.test.ts +49 -49
- package/src/utils/update-elements-with-issues.ts +96 -71
- package/src/validate-options.ts +91 -38
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const accentedUrl = 'https://www.
|
|
1
|
+
export const accentedUrl = 'https://www.accented.dev';
|
|
2
2
|
export const issuesUrl = 'https://github.com/pomerantsev/accented/issues';
|
|
3
3
|
export const getAccentedElementNames = (name: string) => [`${name}-trigger`, `${name}-dialog`];
|
package/src/dom-updater.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { effect } from '@preact/signals-core';
|
|
2
|
+
import { primaryColor } from './common/tokens.js';
|
|
2
3
|
import { extendedElementsWithIssues, rootNodes } from './state.js';
|
|
3
|
-
import type { ExtendedElementWithIssues } from './types';
|
|
4
|
-
import areElementsWithIssuesEqual from './utils/are-elements-with-issues-equal.js';
|
|
5
|
-
import supportsAnchorPositioning from './utils/supports-anchor-positioning.js';
|
|
4
|
+
import type { ExtendedElementWithIssues } from './types.ts';
|
|
5
|
+
import { areElementsWithIssuesEqual } from './utils/are-elements-with-issues-equal.js';
|
|
6
6
|
import { isDocument, isDocumentFragment, isShadowRoot } from './utils/dom-helpers.js';
|
|
7
|
-
import getParent from './utils/get-parent.js';
|
|
7
|
+
import { getParent } from './utils/get-parent.js';
|
|
8
|
+
import { supportsAnchorPositioning } from './utils/supports-anchor-positioning.js';
|
|
8
9
|
|
|
9
10
|
const shouldInsertTriggerInsideElement = (element: Element): boolean => {
|
|
10
11
|
/**
|
|
@@ -30,17 +31,17 @@ const shouldInsertTriggerInsideElement = (element: Element): boolean => {
|
|
|
30
31
|
return noParent || isTableCell || isSummary;
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
export
|
|
34
|
+
export function createDomUpdater(name: string, intersectionObserver?: IntersectionObserver) {
|
|
34
35
|
const attrName = `data-${name}`;
|
|
35
36
|
|
|
36
|
-
function getAnchorNames
|
|
37
|
+
function getAnchorNames(anchorNameValue: string) {
|
|
37
38
|
return anchorNameValue
|
|
38
39
|
.split(',')
|
|
39
|
-
.map(anchorName => anchorName.trim())
|
|
40
|
-
.filter(anchorName => anchorName.startsWith('--'));
|
|
40
|
+
.map((anchorName) => anchorName.trim())
|
|
41
|
+
.filter((anchorName) => anchorName.startsWith('--'));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function setAnchorName
|
|
44
|
+
function setAnchorName(elementWithIssues: ExtendedElementWithIssues) {
|
|
44
45
|
const { element, id, anchorNameValue } = elementWithIssues;
|
|
45
46
|
const anchorNames = getAnchorNames(anchorNameValue);
|
|
46
47
|
if (anchorNames.length > 0) {
|
|
@@ -50,7 +51,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
function removeAnchorName
|
|
54
|
+
function removeAnchorName(elementWithIssues: ExtendedElementWithIssues) {
|
|
54
55
|
const { element, anchorNameValue } = elementWithIssues;
|
|
55
56
|
const anchorNames = getAnchorNames(anchorNameValue);
|
|
56
57
|
if (anchorNames.length > 0) {
|
|
@@ -60,7 +61,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function setIssues
|
|
64
|
+
function setIssues(extendedElementsWithIssues: Array<ExtendedElementWithIssues>) {
|
|
64
65
|
for (const elementWithIssues of extendedElementsWithIssues) {
|
|
65
66
|
if (elementWithIssues.skipRender) {
|
|
66
67
|
continue;
|
|
@@ -81,7 +82,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function removeIssues
|
|
85
|
+
function removeIssues(extendedElementsWithIssues: Array<ExtendedElementWithIssues>) {
|
|
85
86
|
for (const elementWithIssues of extendedElementsWithIssues) {
|
|
86
87
|
if (elementWithIssues.skipRender) {
|
|
87
88
|
continue;
|
|
@@ -103,7 +104,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
103
104
|
:root {
|
|
104
105
|
/* Ensure that the primary / secondary color combination meets WCAG 1.4.3 Contrast (Minimum) */
|
|
105
106
|
/* OKLCH stuff: https://oklch.com/ */
|
|
106
|
-
--${name}-primary-color:
|
|
107
|
+
--${name}-primary-color: ${primaryColor};
|
|
107
108
|
--${name}-secondary-color: oklch(0.98 0 0);
|
|
108
109
|
--${name}-outline-width: 2px;
|
|
109
110
|
--${name}-outline-style: solid;
|
|
@@ -124,8 +125,10 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
124
125
|
|
|
125
126
|
const disposeOfStyleSheetsEffect = effect(() => {
|
|
126
127
|
const newRootNodes = rootNodes.value;
|
|
127
|
-
const addedRootNodes = [...newRootNodes].filter(rootNode => !previousRootNodes.has(rootNode));
|
|
128
|
-
const removedRootNodes = [...previousRootNodes].filter(
|
|
128
|
+
const addedRootNodes = [...newRootNodes].filter((rootNode) => !previousRootNodes.has(rootNode));
|
|
129
|
+
const removedRootNodes = [...previousRootNodes].filter(
|
|
130
|
+
(rootNode) => !newRootNodes.has(rootNode),
|
|
131
|
+
);
|
|
129
132
|
for (const rootNode of addedRootNodes) {
|
|
130
133
|
if (isDocument(rootNode) || (isDocumentFragment(rootNode) && isShadowRoot(rootNode))) {
|
|
131
134
|
rootNode.adoptedStyleSheets.push(stylesheet);
|
|
@@ -140,11 +143,15 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
|
|
|
140
143
|
});
|
|
141
144
|
|
|
142
145
|
const disposeOfElementsEffect = effect(() => {
|
|
143
|
-
const added = extendedElementsWithIssues.value.filter(elementWithIssues => {
|
|
144
|
-
return !previousExtendedElementsWithIssues.some(previousElementWithIssues =>
|
|
146
|
+
const added = extendedElementsWithIssues.value.filter((elementWithIssues) => {
|
|
147
|
+
return !previousExtendedElementsWithIssues.some((previousElementWithIssues) =>
|
|
148
|
+
areElementsWithIssuesEqual(previousElementWithIssues, elementWithIssues),
|
|
149
|
+
);
|
|
145
150
|
});
|
|
146
|
-
const removed = previousExtendedElementsWithIssues.filter(previousElementWithIssues => {
|
|
147
|
-
return !extendedElementsWithIssues.value.some(elementWithIssues =>
|
|
151
|
+
const removed = previousExtendedElementsWithIssues.filter((previousElementWithIssues) => {
|
|
152
|
+
return !extendedElementsWithIssues.value.some((elementWithIssues) =>
|
|
153
|
+
areElementsWithIssuesEqual(elementWithIssues, previousElementWithIssues),
|
|
154
|
+
);
|
|
148
155
|
});
|
|
149
156
|
removeIssues(removed);
|
|
150
157
|
setIssues(added);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Issue } from '../types';
|
|
2
1
|
import type { Signal } from '@preact/signals-core';
|
|
3
|
-
import getElementHtml from '../utils/get-element-html.js';
|
|
4
2
|
import { accentedUrl } from '../constants.js';
|
|
5
|
-
import logAndRethrow from '../log-and-rethrow.js';
|
|
3
|
+
import { logAndRethrow } from '../log-and-rethrow.js';
|
|
4
|
+
import type { Issue } from '../types.ts';
|
|
5
|
+
import { getElementHtml } from '../utils/get-element-html.js';
|
|
6
|
+
import { isNonEmpty } from '../utils/is-non-empty.js';
|
|
6
7
|
|
|
7
8
|
export interface AccentedDialog extends HTMLElement {
|
|
8
9
|
issues: Signal<Array<Issue>> | undefined;
|
|
@@ -13,7 +14,7 @@ export interface AccentedDialog extends HTMLElement {
|
|
|
13
14
|
|
|
14
15
|
// We want Accented to not throw an error in Node, and use static imports,
|
|
15
16
|
// so we can't export `class extends HTMLElement` because HTMLElement is not available in Node.
|
|
16
|
-
export
|
|
17
|
+
export const getAccentedDialog = () => {
|
|
17
18
|
const dialogTemplate = document.createElement('template');
|
|
18
19
|
dialogTemplate.innerHTML = `
|
|
19
20
|
<dialog dir="ltr" lang="en" aria-labelledby="title">
|
|
@@ -247,11 +248,11 @@ export default () => {
|
|
|
247
248
|
|
|
248
249
|
element: Element | undefined;
|
|
249
250
|
|
|
250
|
-
open
|
|
251
|
+
open = false;
|
|
251
252
|
|
|
252
253
|
constructor() {
|
|
254
|
+
super();
|
|
253
255
|
try {
|
|
254
|
-
super();
|
|
255
256
|
this.attachShadow({ mode: 'open' });
|
|
256
257
|
const content = dialogTemplate.content.cloneNode(true);
|
|
257
258
|
if (this.shadowRoot) {
|
|
@@ -270,31 +271,43 @@ export default () => {
|
|
|
270
271
|
const dialog = shadowRoot.querySelector('dialog');
|
|
271
272
|
const closeButton = shadowRoot.querySelector('#close');
|
|
272
273
|
this.#abortController = new AbortController();
|
|
273
|
-
closeButton?.addEventListener(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
dialog?.addEventListener('click', (event) => {
|
|
282
|
-
try {
|
|
283
|
-
this.#onDialogClick(event);
|
|
284
|
-
} catch (error) {
|
|
285
|
-
logAndRethrow(error);
|
|
286
|
-
}
|
|
287
|
-
}, { signal: this.#abortController.signal });
|
|
288
|
-
|
|
289
|
-
dialog?.addEventListener('keydown', (event) => {
|
|
290
|
-
try {
|
|
291
|
-
if (event.key === 'Escape') {
|
|
292
|
-
event.stopPropagation();
|
|
274
|
+
closeButton?.addEventListener(
|
|
275
|
+
'click',
|
|
276
|
+
() => {
|
|
277
|
+
try {
|
|
278
|
+
dialog?.close();
|
|
279
|
+
} catch (error) {
|
|
280
|
+
logAndRethrow(error);
|
|
293
281
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
282
|
+
},
|
|
283
|
+
{ signal: this.#abortController.signal },
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
dialog?.addEventListener(
|
|
287
|
+
'click',
|
|
288
|
+
(event) => {
|
|
289
|
+
try {
|
|
290
|
+
this.#onDialogClick(event);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logAndRethrow(error);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{ signal: this.#abortController.signal },
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
dialog?.addEventListener(
|
|
299
|
+
'keydown',
|
|
300
|
+
(event) => {
|
|
301
|
+
try {
|
|
302
|
+
if (event.key === 'Escape') {
|
|
303
|
+
event.stopPropagation();
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
logAndRethrow(error);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{ signal: this.#abortController.signal },
|
|
310
|
+
);
|
|
298
311
|
|
|
299
312
|
if (this.issues) {
|
|
300
313
|
const issues = this.issues.value;
|
|
@@ -307,18 +320,23 @@ export default () => {
|
|
|
307
320
|
const impact = issueContent.querySelector('.impact');
|
|
308
321
|
const description = issueContent.querySelector('.description');
|
|
309
322
|
if (title && impact && description) {
|
|
310
|
-
title.textContent = issue.title
|
|
323
|
+
title.textContent = `${issue.title} (${issue.id})`;
|
|
311
324
|
title.href = issue.url;
|
|
312
325
|
|
|
313
|
-
impact.textContent =
|
|
326
|
+
impact.textContent = `User impact: ${issue.impact}`;
|
|
314
327
|
impact.setAttribute('data-impact', String(issue.impact));
|
|
315
328
|
|
|
316
329
|
const descriptionItems = issue.description.split(/\n\s*/);
|
|
317
330
|
const descriptionContent = descriptionTemplate.content.cloneNode(true) as Element;
|
|
318
331
|
const descriptionTitle = descriptionContent.querySelector('span');
|
|
319
332
|
const descriptionList = descriptionContent.querySelector('ul');
|
|
320
|
-
if (
|
|
321
|
-
descriptionTitle
|
|
333
|
+
if (
|
|
334
|
+
descriptionTitle &&
|
|
335
|
+
descriptionList &&
|
|
336
|
+
isNonEmpty(descriptionItems) &&
|
|
337
|
+
descriptionItems.length > 1
|
|
338
|
+
) {
|
|
339
|
+
descriptionTitle.textContent = descriptionItems[0];
|
|
322
340
|
for (const descriptionItem of descriptionItems.slice(1)) {
|
|
323
341
|
const li = document.createElement('li');
|
|
324
342
|
li.textContent = descriptionItem;
|
|
@@ -339,14 +357,18 @@ export default () => {
|
|
|
339
357
|
}
|
|
340
358
|
}
|
|
341
359
|
|
|
342
|
-
dialog?.addEventListener(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
360
|
+
dialog?.addEventListener(
|
|
361
|
+
'close',
|
|
362
|
+
() => {
|
|
363
|
+
try {
|
|
364
|
+
this.open = false;
|
|
365
|
+
this.dispatchEvent(new Event('close'));
|
|
366
|
+
} catch (error) {
|
|
367
|
+
logAndRethrow(error);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{ signal: this.#abortController.signal },
|
|
371
|
+
);
|
|
350
372
|
}
|
|
351
373
|
} catch (error) {
|
|
352
374
|
logAndRethrow(error);
|
|
@@ -375,7 +397,11 @@ export default () => {
|
|
|
375
397
|
|
|
376
398
|
#onDialogClick(event: MouseEvent) {
|
|
377
399
|
const dialog = event.currentTarget as HTMLDialogElement;
|
|
378
|
-
if (
|
|
400
|
+
if (
|
|
401
|
+
!dialog ||
|
|
402
|
+
typeof dialog.getBoundingClientRect !== 'function' ||
|
|
403
|
+
typeof dialog.close !== 'function'
|
|
404
|
+
) {
|
|
379
405
|
return;
|
|
380
406
|
}
|
|
381
407
|
const rect = dialog.getBoundingClientRect();
|
|
@@ -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,6 +42,8 @@ export default (name: string) => {
|
|
|
44
42
|
#trigger {
|
|
45
43
|
pointer-events: auto;
|
|
46
44
|
|
|
45
|
+
user-select: none;
|
|
46
|
+
|
|
47
47
|
margin-inline-start: auto;
|
|
48
48
|
margin-inline-end: 4px;
|
|
49
49
|
margin-block-start: 4px;
|
|
@@ -105,8 +105,8 @@ export default (name: string) => {
|
|
|
105
105
|
visible: Signal<boolean> | undefined;
|
|
106
106
|
|
|
107
107
|
constructor() {
|
|
108
|
+
super();
|
|
108
109
|
try {
|
|
109
|
-
super();
|
|
110
110
|
this.attachShadow({ mode: 'open' });
|
|
111
111
|
const content = template.content.cloneNode(true);
|
|
112
112
|
if (this.shadowRoot) {
|
|
@@ -138,42 +138,50 @@ export default (name: string) => {
|
|
|
138
138
|
|
|
139
139
|
if (this.element) {
|
|
140
140
|
this.#elementMutationObserver.observe(this.element, {
|
|
141
|
-
attributes: true
|
|
141
|
+
attributes: true,
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
this.#abortController = new AbortController();
|
|
146
|
-
trigger?.addEventListener(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
146
|
+
trigger?.addEventListener(
|
|
147
|
+
'click',
|
|
148
|
+
(event) => {
|
|
149
|
+
try {
|
|
150
|
+
// event.preventDefault() ensures that if the issue is within a link,
|
|
151
|
+
// the link's default behavior (following the URL) is prevented.
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
|
|
154
|
+
// event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
|
|
155
|
+
// (a link, or a button, or anything else), it doesn't get triggered.
|
|
156
|
+
event.stopPropagation();
|
|
157
|
+
|
|
158
|
+
// We append the dialog when the button is clicked,
|
|
159
|
+
// and remove it from the DOM when the dialog is closed.
|
|
160
|
+
// This gives us a performance improvement since Axe
|
|
161
|
+
// scan time seems to depend on the number of elements in the DOM.
|
|
162
|
+
if (this.dialog) {
|
|
163
|
+
this.#dialogCloseAbortController = new AbortController();
|
|
164
|
+
document.body.append(this.dialog);
|
|
165
|
+
this.dialog.showModal();
|
|
166
|
+
this.dialog.addEventListener(
|
|
167
|
+
'close',
|
|
168
|
+
() => {
|
|
169
|
+
try {
|
|
170
|
+
this.dialog?.remove();
|
|
171
|
+
this.#dialogCloseAbortController?.abort();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logAndRethrow(error);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{ signal: this.#dialogCloseAbortController.signal },
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logAndRethrow(error);
|
|
172
181
|
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}, { signal: this.#abortController.signal });
|
|
182
|
+
},
|
|
183
|
+
{ signal: this.#abortController.signal },
|
|
184
|
+
);
|
|
177
185
|
|
|
178
186
|
if (!supportsAnchorPositioning(window)) {
|
|
179
187
|
this.#disposeOfPositionEffect = effect(() => {
|
|
@@ -185,11 +193,14 @@ export default (name: string) => {
|
|
|
185
193
|
this.style.setProperty('height', `${position.height}px`, 'important');
|
|
186
194
|
}
|
|
187
195
|
});
|
|
188
|
-
|
|
189
|
-
this.#disposeOfVisibilityEffect = effect(() => {
|
|
190
|
-
this.style.setProperty('visibility', this.visible?.value ? 'visible' : 'hidden', 'important');
|
|
191
|
-
});
|
|
192
196
|
}
|
|
197
|
+
this.#disposeOfVisibilityEffect = effect(() => {
|
|
198
|
+
this.style.setProperty(
|
|
199
|
+
'visibility',
|
|
200
|
+
this.visible?.value ? 'visible' : 'hidden',
|
|
201
|
+
'important',
|
|
202
|
+
);
|
|
203
|
+
});
|
|
193
204
|
}
|
|
194
205
|
} catch (error) {
|
|
195
206
|
logAndRethrow(error);
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import logAndRethrow from './log-and-rethrow.js';
|
|
2
|
-
import recalculatePositions from './utils/recalculate-positions.js';
|
|
1
|
+
import { logAndRethrow } from './log-and-rethrow.js';
|
|
2
|
+
import { recalculatePositions } from './utils/recalculate-positions.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function setupResizeListener() {
|
|
5
5
|
const abortController = new AbortController();
|
|
6
|
-
window.addEventListener(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
window.addEventListener(
|
|
7
|
+
'fullscreenchange',
|
|
8
|
+
() => {
|
|
9
|
+
try {
|
|
10
|
+
recalculatePositions();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
logAndRethrow(error);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{ signal: abortController.signal },
|
|
16
|
+
);
|
|
13
17
|
|
|
14
18
|
return () => {
|
|
15
19
|
abortController.abort();
|
|
16
20
|
};
|
|
17
|
-
}
|
|
21
|
+
}
|
|
@@ -1,28 +1,39 @@
|
|
|
1
|
-
import logAndRethrow from './log-and-rethrow.js';
|
|
1
|
+
import { logAndRethrow } from './log-and-rethrow.js';
|
|
2
2
|
import { extendedElementsWithIssues } from './state.js';
|
|
3
|
-
import getElementPosition from './utils/get-element-position.js';
|
|
3
|
+
import { getElementPosition } from './utils/get-element-position.js';
|
|
4
|
+
import { supportsAnchorPositioning } from './utils/supports-anchor-positioning.js';
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
const intersectionObserver = new IntersectionObserver(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export function setupIntersectionObserver() {
|
|
7
|
+
const intersectionObserver = new IntersectionObserver(
|
|
8
|
+
(entries) => {
|
|
9
|
+
try {
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const extendedElementWithIssues = extendedElementsWithIssues.value.find(
|
|
12
|
+
(el) => el.element === entry.target,
|
|
13
|
+
);
|
|
14
|
+
if (extendedElementWithIssues) {
|
|
15
|
+
// We initially treated setting visibility in the intersection observer
|
|
16
|
+
// as a fallback option for browsers that don't support `position-visibility`,
|
|
17
|
+
// but then we realized that this `position-visibility` actually works
|
|
18
|
+
// in an unexpected way when the container has `overflow: visible`.
|
|
19
|
+
// So now we always set visibility in the intersection observer.
|
|
20
|
+
extendedElementWithIssues.visible.value = entry.isIntersecting;
|
|
21
|
+
if (entry.isIntersecting && !supportsAnchorPositioning(window)) {
|
|
22
|
+
extendedElementWithIssues.position.value = getElementPosition(entry.target, window);
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logAndRethrow(error);
|
|
16
28
|
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}, { threshold: 0 });
|
|
29
|
+
},
|
|
30
|
+
{ threshold: 0 },
|
|
31
|
+
);
|
|
21
32
|
|
|
22
33
|
return {
|
|
23
34
|
intersectionObserver,
|
|
24
35
|
disconnect: () => {
|
|
25
36
|
intersectionObserver.disconnect();
|
|
26
|
-
}
|
|
37
|
+
},
|
|
27
38
|
};
|
|
28
39
|
}
|
package/src/log-and-rethrow.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { issuesUrl } from './constants.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function logAndRethrow(error: unknown) {
|
|
4
4
|
console.error(
|
|
5
|
-
`Accented threw an error (see below). Try updating your browser to the latest version.
|
|
6
|
-
`If you’re still seeing the error, file an issue at ${issuesUrl}.`
|
|
5
|
+
`Accented threw an error (see below). Try updating your browser to the latest version. If you’re still seeing the error, file an issue at ${issuesUrl}.`,
|
|
7
6
|
);
|
|
8
7
|
throw error;
|
|
9
8
|
}
|
package/src/logger.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { effect } from '@preact/signals-core';
|
|
2
|
-
import { elementsWithIssues, enabled } from './state.js';
|
|
3
2
|
import { accentedUrl } from './constants.js';
|
|
4
|
-
import
|
|
3
|
+
import { elementsWithIssues, enabled } from './state.js';
|
|
4
|
+
import type { ElementWithIssues } from './types.ts';
|
|
5
5
|
|
|
6
6
|
function filterPropsForOutput(elements: Array<ElementWithIssues>) {
|
|
7
7
|
return elements.map(({ element, issues }) => ({ element, issues }));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
10
|
+
export function createLogger() {
|
|
12
11
|
let firstRun = true;
|
|
13
12
|
|
|
14
13
|
return effect(() => {
|
|
@@ -18,10 +17,13 @@ export default function createLogger() {
|
|
|
18
17
|
|
|
19
18
|
const elementCount = elementsWithIssues.value.length;
|
|
20
19
|
if (elementCount > 0) {
|
|
21
|
-
const issueCount = elementsWithIssues.value.reduce(
|
|
20
|
+
const issueCount = elementsWithIssues.value.reduce(
|
|
21
|
+
(acc, { issues }) => acc + issues.length,
|
|
22
|
+
0,
|
|
23
|
+
);
|
|
22
24
|
console.log(
|
|
23
25
|
`${issueCount} accessibility issue${issueCount === 1 ? '' : 's'} found in ${elementCount} element${issueCount === 1 ? '' : 's'} (Accented, ${accentedUrl}):\n`,
|
|
24
|
-
filterPropsForOutput(elementsWithIssues.value)
|
|
26
|
+
filterPropsForOutput(elementsWithIssues.value),
|
|
25
27
|
);
|
|
26
28
|
} else {
|
|
27
29
|
if (firstRun) {
|
package/src/register-elements.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { getAccentedDialog } from './elements/accented-dialog.js';
|
|
2
|
+
import { getAccentedTrigger } from './elements/accented-trigger.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function registerElements(name: string): void {
|
|
5
5
|
const elements = [
|
|
6
6
|
{
|
|
7
7
|
elementName: `${name}-trigger`,
|
|
8
|
-
Component: getAccentedTrigger(name)
|
|
8
|
+
Component: getAccentedTrigger(name),
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
elementName: `${name}-dialog`,
|
|
12
|
-
Component: getAccentedDialog()
|
|
13
|
-
}
|
|
12
|
+
Component: getAccentedDialog(),
|
|
13
|
+
},
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
for (const { elementName, Component } of elements) {
|
|
@@ -18,4 +18,4 @@ export default function registerElements(name: string): void {
|
|
|
18
18
|
customElements.define(elementName, Component);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
}
|
|
21
|
+
}
|
package/src/resize-listener.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import logAndRethrow from './log-and-rethrow.js';
|
|
2
|
-
import recalculatePositions from './utils/recalculate-positions.js';
|
|
1
|
+
import { logAndRethrow } from './log-and-rethrow.js';
|
|
2
|
+
import { recalculatePositions } from './utils/recalculate-positions.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function setupResizeListener() {
|
|
5
5
|
const abortController = new AbortController();
|
|
6
|
-
window.addEventListener(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
window.addEventListener(
|
|
7
|
+
'resize',
|
|
8
|
+
() => {
|
|
9
|
+
try {
|
|
10
|
+
recalculatePositions();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
logAndRethrow(error);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{ signal: abortController.signal },
|
|
16
|
+
);
|
|
13
17
|
|
|
14
18
|
return () => {
|
|
15
19
|
abortController.abort();
|
|
16
20
|
};
|
|
17
|
-
}
|
|
21
|
+
}
|