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,19 +1,21 @@
|
|
|
1
|
-
import type { Issue } from '../types';
|
|
2
1
|
import type { Signal } from '@preact/signals-core';
|
|
3
|
-
import {
|
|
4
|
-
import getElementHtml from '../utils/get-element-html.js';
|
|
2
|
+
import { colorDark, colorLight, fontSystemMono, fontSystemSans } from '../common/tokens.js';
|
|
5
3
|
import { accentedUrl } from '../constants.js';
|
|
6
|
-
import logAndRethrow from '../log-and-rethrow.js';
|
|
4
|
+
import { logAndRethrow } from '../log-and-rethrow.js';
|
|
5
|
+
import type { Issue } from '../types.ts';
|
|
6
|
+
import { getElementHtml } from '../utils/get-element-html.js';
|
|
7
|
+
import { isNonEmpty } from '../utils/is-non-empty.js';
|
|
7
8
|
|
|
8
9
|
export interface AccentedDialog extends HTMLElement {
|
|
9
10
|
issues: Signal<Array<Issue>> | undefined;
|
|
10
11
|
element: Element | undefined;
|
|
11
12
|
showModal: () => void;
|
|
13
|
+
open: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
// We want Accented to not throw an error in Node, and use static imports,
|
|
15
17
|
// so we can't export `class extends HTMLElement` because HTMLElement is not available in Node.
|
|
16
|
-
export
|
|
18
|
+
export const getAccentedDialog = () => {
|
|
17
19
|
const dialogTemplate = document.createElement('template');
|
|
18
20
|
dialogTemplate.innerHTML = `
|
|
19
21
|
<dialog dir="ltr" lang="en" aria-labelledby="title">
|
|
@@ -56,20 +58,45 @@ export default () => {
|
|
|
56
58
|
:host {
|
|
57
59
|
all: initial !important;
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
--
|
|
61
|
-
--
|
|
61
|
+
/* OKLCH stuff: https://oklch.com/ */
|
|
62
|
+
--light-color: ${colorLight};
|
|
63
|
+
--dark-color: ${colorDark};
|
|
62
64
|
|
|
63
|
-
--
|
|
64
|
-
--
|
|
65
|
-
|
|
66
|
-
--impact-
|
|
65
|
+
--background-color: light-dark(var(--light-color), var(--dark-color));
|
|
66
|
+
--text-color: light-dark(var(--dark-color), var(--light-color));
|
|
67
|
+
|
|
68
|
+
--impact-lightness: 0.80;
|
|
69
|
+
--focus-lightness: 0.45;
|
|
70
|
+
@media (prefers-color-scheme: dark) {
|
|
71
|
+
--impact-lightness: 0.45;
|
|
72
|
+
--focus-lightness: 0.80;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
--blue-hue: 230;
|
|
76
|
+
--gold-hue: 90;
|
|
77
|
+
--red-hue: 0;
|
|
78
|
+
|
|
79
|
+
/* Contrasts with background. */
|
|
80
|
+
--focus-color: oklch(var(--focus-lightness) 0.25 var(--blue-hue));
|
|
81
|
+
|
|
82
|
+
--impact-chroma: 0.16;
|
|
83
|
+
|
|
84
|
+
--impact-moderate-hue: var(--blue-hue);
|
|
85
|
+
--impact-serious-hue: var(--gold-hue);
|
|
86
|
+
--impact-critical-hue: var(--red-hue);
|
|
87
|
+
|
|
88
|
+
--impact-minor-color: oklch(var(--impact-lightness) 0 0);
|
|
89
|
+
--impact-moderate-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-moderate-hue));
|
|
90
|
+
--impact-serious-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-serious-hue));
|
|
91
|
+
--impact-critical-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-critical-hue));
|
|
92
|
+
|
|
93
|
+
--base-size: max(1rem, 16px);
|
|
67
94
|
|
|
68
95
|
/* Spacing and typography custom props, inspired by https://utopia.fyi (simplified). */
|
|
69
96
|
|
|
70
97
|
/* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1240,16,1.2,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
|
|
71
98
|
--ratio: 1.2;
|
|
72
|
-
--step-0:
|
|
99
|
+
--step-0: var(--base-size);
|
|
73
100
|
--step-1: calc(var(--step-0) * var(--ratio));
|
|
74
101
|
--step-2: calc(var(--step-1) * var(--ratio));
|
|
75
102
|
--step-3: calc(var(--step-2) * var(--ratio));
|
|
@@ -77,15 +104,15 @@ export default () => {
|
|
|
77
104
|
--step--1: calc(var(--step-0) / var(--ratio));
|
|
78
105
|
|
|
79
106
|
/* @link https://utopia.fyi/space/calculator?c=320,16,1.2,1240,16,1.2,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
|
|
80
|
-
--space-3xs: 0.
|
|
81
|
-
--space-2xs: 0.
|
|
82
|
-
--space-xs: 0.
|
|
83
|
-
--space-s:
|
|
84
|
-
--space-m: 1.
|
|
85
|
-
--space-l:
|
|
86
|
-
--space-xl:
|
|
87
|
-
--space-2xl:
|
|
88
|
-
--space-3xl:
|
|
107
|
+
--space-3xs: calc(0.25 * var(--base-size));
|
|
108
|
+
--space-2xs: calc(0.5 * var(--base-size));
|
|
109
|
+
--space-xs: calc(0.75 * var(--base-size));
|
|
110
|
+
--space-s: var(--base-size);
|
|
111
|
+
--space-m: calc(1.5 * var(--base-size));
|
|
112
|
+
--space-l: calc(2 * var(--base-size));
|
|
113
|
+
--space-xl: calc(3 * var(--base-size));
|
|
114
|
+
--space-2xl: calc(4 * var(--base-size));
|
|
115
|
+
--space-3xl: calc(6 * var(--base-size));
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
a[href], button {
|
|
@@ -98,6 +125,7 @@ export default () => {
|
|
|
98
125
|
outline-style: solid;
|
|
99
126
|
}
|
|
100
127
|
|
|
128
|
+
/* We should probably be comfortable with showing these styles on non-hover devices. */
|
|
101
129
|
&:hover:not(:focus-visible) {
|
|
102
130
|
outline-style: dashed;
|
|
103
131
|
}
|
|
@@ -114,14 +142,17 @@ export default () => {
|
|
|
114
142
|
dialog {
|
|
115
143
|
box-sizing: border-box;
|
|
116
144
|
overflow-wrap: break-word;
|
|
117
|
-
font-family:
|
|
145
|
+
font-family: ${fontSystemSans};
|
|
118
146
|
line-height: 1.5;
|
|
119
|
-
|
|
120
|
-
color: var(--
|
|
147
|
+
text-wrap: pretty;
|
|
148
|
+
background-color: var(--background-color);
|
|
149
|
+
color: var(--text-color);
|
|
121
150
|
border: 2px solid currentColor;
|
|
122
151
|
padding: var(--space-l);
|
|
123
152
|
inline-size: min(90ch, calc(100% - var(--space-s)* 2));
|
|
124
153
|
max-block-size: calc(100% - var(--space-s) * 2);
|
|
154
|
+
|
|
155
|
+
color-scheme: light dark;
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
#button-container {
|
|
@@ -129,8 +160,8 @@ export default () => {
|
|
|
129
160
|
}
|
|
130
161
|
|
|
131
162
|
#close {
|
|
132
|
-
background-color: var(--
|
|
133
|
-
color: var(--
|
|
163
|
+
background-color: var(--background-color);
|
|
164
|
+
color: var(--text-color);
|
|
134
165
|
border: 2px solid currentColor;
|
|
135
166
|
padding-inline: var(--space-2xs);
|
|
136
167
|
aspect-ratio: 1 / 1;
|
|
@@ -151,8 +182,7 @@ export default () => {
|
|
|
151
182
|
}
|
|
152
183
|
|
|
153
184
|
code {
|
|
154
|
-
|
|
155
|
-
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
|
|
185
|
+
font-family: ${fontSystemMono};
|
|
156
186
|
font-size: var(--step--1);
|
|
157
187
|
}
|
|
158
188
|
|
|
@@ -167,7 +197,7 @@ export default () => {
|
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
a {
|
|
170
|
-
font-weight:
|
|
200
|
+
font-weight: 500;
|
|
171
201
|
}
|
|
172
202
|
}
|
|
173
203
|
|
|
@@ -213,19 +243,17 @@ export default () => {
|
|
|
213
243
|
`);
|
|
214
244
|
|
|
215
245
|
return class extends HTMLElement implements AccentedDialog {
|
|
216
|
-
#disposeOfEffect: (() => void) | undefined;
|
|
217
|
-
|
|
218
246
|
#abortController: AbortController | undefined;
|
|
219
247
|
|
|
220
248
|
issues: Signal<Array<Issue>> | undefined;
|
|
221
249
|
|
|
222
250
|
element: Element | undefined;
|
|
223
251
|
|
|
224
|
-
|
|
252
|
+
open = false;
|
|
225
253
|
|
|
226
254
|
constructor() {
|
|
255
|
+
super();
|
|
227
256
|
try {
|
|
228
|
-
super();
|
|
229
257
|
this.attachShadow({ mode: 'open' });
|
|
230
258
|
const content = dialogTemplate.content.cloneNode(true);
|
|
231
259
|
if (this.shadowRoot) {
|
|
@@ -244,96 +272,104 @@ export default () => {
|
|
|
244
272
|
const dialog = shadowRoot.querySelector('dialog');
|
|
245
273
|
const closeButton = shadowRoot.querySelector('#close');
|
|
246
274
|
this.#abortController = new AbortController();
|
|
247
|
-
closeButton?.addEventListener(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
275
|
+
closeButton?.addEventListener(
|
|
276
|
+
'click',
|
|
277
|
+
() => {
|
|
278
|
+
try {
|
|
279
|
+
dialog?.close();
|
|
280
|
+
} catch (error) {
|
|
281
|
+
logAndRethrow(error);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{ signal: this.#abortController.signal },
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
dialog?.addEventListener(
|
|
288
|
+
'click',
|
|
289
|
+
(event) => {
|
|
290
|
+
try {
|
|
291
|
+
this.#onDialogClick(event);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logAndRethrow(error);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{ signal: this.#abortController.signal },
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
dialog?.addEventListener(
|
|
300
|
+
'keydown',
|
|
301
|
+
(event) => {
|
|
302
|
+
try {
|
|
303
|
+
if (event.key === 'Escape') {
|
|
304
|
+
event.stopPropagation();
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
logAndRethrow(error);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
{ signal: this.#abortController.signal },
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (this.issues) {
|
|
314
|
+
const issues = this.issues.value;
|
|
315
|
+
const issuesList = shadowRoot.getElementById('issues');
|
|
316
|
+
if (issuesList) {
|
|
317
|
+
issuesList.innerHTML = '';
|
|
318
|
+
for (const issue of issues) {
|
|
319
|
+
const issueContent = issueTemplate.content.cloneNode(true) as Element;
|
|
320
|
+
const title = issueContent.querySelector('a');
|
|
321
|
+
const impact = issueContent.querySelector('.impact');
|
|
322
|
+
const description = issueContent.querySelector('.description');
|
|
323
|
+
if (title && impact && description) {
|
|
324
|
+
title.textContent = `${issue.title} (${issue.id})`;
|
|
325
|
+
title.href = issue.url;
|
|
326
|
+
|
|
327
|
+
impact.textContent = `User impact: ${issue.impact}`;
|
|
328
|
+
impact.setAttribute('data-impact', String(issue.impact));
|
|
329
|
+
|
|
330
|
+
const descriptionItems = issue.description.split(/\n\s*/);
|
|
331
|
+
const descriptionContent = descriptionTemplate.content.cloneNode(true) as Element;
|
|
332
|
+
const descriptionTitle = descriptionContent.querySelector('span');
|
|
333
|
+
const descriptionList = descriptionContent.querySelector('ul');
|
|
334
|
+
if (
|
|
335
|
+
descriptionTitle &&
|
|
336
|
+
descriptionList &&
|
|
337
|
+
isNonEmpty(descriptionItems) &&
|
|
338
|
+
descriptionItems.length > 1
|
|
339
|
+
) {
|
|
340
|
+
descriptionTitle.textContent = descriptionItems[0];
|
|
341
|
+
for (const descriptionItem of descriptionItems.slice(1)) {
|
|
342
|
+
const li = document.createElement('li');
|
|
343
|
+
li.textContent = descriptionItem;
|
|
344
|
+
descriptionList.appendChild(li);
|
|
293
345
|
}
|
|
346
|
+
description.appendChild(descriptionContent);
|
|
294
347
|
}
|
|
295
|
-
issuesList.appendChild(issueContent);
|
|
296
348
|
}
|
|
349
|
+
issuesList.appendChild(issueContent);
|
|
297
350
|
}
|
|
298
351
|
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const updateElementHtml = () => {
|
|
302
|
-
if (this.element) {
|
|
303
|
-
const elementHtmlContainer = shadowRoot.getElementById('element-html');
|
|
304
|
-
if (elementHtmlContainer) {
|
|
305
|
-
elementHtmlContainer.textContent = getElementHtml(this.element);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
updateElementHtml();
|
|
352
|
+
}
|
|
311
353
|
|
|
312
|
-
this.#elementMutationObserver = new MutationObserver(() => {
|
|
313
|
-
try {
|
|
314
|
-
updateElementHtml();
|
|
315
|
-
} catch (error) {
|
|
316
|
-
logAndRethrow(error);
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
354
|
if (this.element) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.#elementMutationObserver.observe(this.element, {
|
|
325
|
-
attributes: true,
|
|
326
|
-
childList: true
|
|
327
|
-
});
|
|
355
|
+
const elementHtmlContainer = shadowRoot.getElementById('element-html');
|
|
356
|
+
if (elementHtmlContainer) {
|
|
357
|
+
elementHtmlContainer.textContent = getElementHtml(this.element);
|
|
358
|
+
}
|
|
328
359
|
}
|
|
329
360
|
|
|
330
|
-
dialog?.addEventListener(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
dialog?.addEventListener(
|
|
362
|
+
'close',
|
|
363
|
+
() => {
|
|
364
|
+
try {
|
|
365
|
+
this.open = false;
|
|
366
|
+
this.dispatchEvent(new Event('close'));
|
|
367
|
+
} catch (error) {
|
|
368
|
+
logAndRethrow(error);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{ signal: this.#abortController.signal },
|
|
372
|
+
);
|
|
337
373
|
}
|
|
338
374
|
} catch (error) {
|
|
339
375
|
logAndRethrow(error);
|
|
@@ -342,15 +378,9 @@ export default () => {
|
|
|
342
378
|
|
|
343
379
|
disconnectedCallback() {
|
|
344
380
|
try {
|
|
345
|
-
if (this.#disposeOfEffect) {
|
|
346
|
-
this.#disposeOfEffect();
|
|
347
|
-
}
|
|
348
381
|
if (this.#abortController) {
|
|
349
382
|
this.#abortController.abort();
|
|
350
383
|
}
|
|
351
|
-
if (this.#elementMutationObserver) {
|
|
352
|
-
this.#elementMutationObserver.disconnect();
|
|
353
|
-
}
|
|
354
384
|
} catch (error) {
|
|
355
385
|
logAndRethrow(error);
|
|
356
386
|
}
|
|
@@ -361,13 +391,18 @@ export default () => {
|
|
|
361
391
|
const dialog = this.shadowRoot.querySelector('dialog');
|
|
362
392
|
if (dialog) {
|
|
363
393
|
dialog.showModal();
|
|
394
|
+
this.open = true;
|
|
364
395
|
}
|
|
365
396
|
}
|
|
366
397
|
}
|
|
367
398
|
|
|
368
399
|
#onDialogClick(event: MouseEvent) {
|
|
369
400
|
const dialog = event.currentTarget as HTMLDialogElement;
|
|
370
|
-
if (
|
|
401
|
+
if (
|
|
402
|
+
!dialog ||
|
|
403
|
+
typeof dialog.getBoundingClientRect !== 'function' ||
|
|
404
|
+
typeof dialog.close !== 'function'
|
|
405
|
+
) {
|
|
371
406
|
return;
|
|
372
407
|
}
|
|
373
408
|
const rect = dialog.getBoundingClientRect();
|
|
@@ -1,9 +1,10 @@
|
|
|
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 logAndRethrow from '../log-and-rethrow.js';
|
|
3
|
+
import { fontSystemSans } from '../common/tokens.js';
|
|
4
|
+
import { logAndRethrow } from '../log-and-rethrow.js';
|
|
5
|
+
import type { Position } from '../types.ts';
|
|
6
|
+
import { supportsAnchorPositioning } from '../utils/supports-anchor-positioning.js';
|
|
7
|
+
import type { AccentedDialog } from './accented-dialog.ts';
|
|
7
8
|
|
|
8
9
|
export interface AccentedTrigger extends HTMLElement {
|
|
9
10
|
element: Element | undefined;
|
|
@@ -12,11 +13,9 @@ export interface AccentedTrigger extends HTMLElement {
|
|
|
12
13
|
visible: Signal<boolean> | undefined;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const triggerSize = 'max(32px, 2rem)';
|
|
16
|
-
|
|
17
16
|
// We want Accented to not throw an error in Node, and use static imports,
|
|
18
17
|
// so we can't export `class extends HTMLElement` because HTMLElement is not available in Node.
|
|
19
|
-
export
|
|
18
|
+
export const getAccentedTrigger = (name: string) => {
|
|
20
19
|
const template = document.createElement('template');
|
|
21
20
|
|
|
22
21
|
// I initially tried creating a CSSStyelSheet object with styles instead of having a <style> element in the template,
|
|
@@ -27,21 +26,38 @@ export default (name: string) => {
|
|
|
27
26
|
template.innerHTML = `
|
|
28
27
|
<style>
|
|
29
28
|
:host {
|
|
29
|
+
--ratio: 1.2;
|
|
30
|
+
--base-size: max(1rem, 16px);
|
|
30
31
|
position: fixed !important;
|
|
32
|
+
inset-inline-start: anchor(self-start) !important;
|
|
31
33
|
inset-inline-end: anchor(self-end) !important;
|
|
32
34
|
inset-block-start: anchor(self-start) !important;
|
|
33
|
-
|
|
34
|
-
position-visibility: anchors-visible !important;
|
|
35
|
+
inset-block-end: anchor(self-end) !important;
|
|
35
36
|
|
|
36
37
|
/* Revert potential effects of white-space: pre; set on a trigger's ancestor. */
|
|
37
38
|
white-space: normal !important;
|
|
39
|
+
|
|
40
|
+
pointer-events: none !important;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
#trigger {
|
|
44
|
+
pointer-events: auto;
|
|
45
|
+
|
|
46
|
+
user-select: none;
|
|
47
|
+
|
|
48
|
+
margin-inline-start: auto;
|
|
49
|
+
margin-inline-end: 4px;
|
|
50
|
+
margin-block-start: 4px;
|
|
51
|
+
|
|
41
52
|
box-sizing: border-box;
|
|
42
|
-
font-
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
font-family: ${fontSystemSans};
|
|
54
|
+
font-size: calc(var(--ratio) * var(--ratio) * var(--base-size));
|
|
55
|
+
inline-size: calc(2 * var(--base-size));
|
|
56
|
+
block-size: calc(2 * var(--base-size));
|
|
57
|
+
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
45
61
|
|
|
46
62
|
/* Make it look better in forced-colors mode. */
|
|
47
63
|
border: 2px solid transparent;
|
|
@@ -49,6 +65,10 @@ export default (name: string) => {
|
|
|
49
65
|
background-color: var(--${name}-primary-color);
|
|
50
66
|
color: var(--${name}-secondary-color);
|
|
51
67
|
|
|
68
|
+
padding: 0;
|
|
69
|
+
|
|
70
|
+
border-radius: calc(0.25 * var(--base-size));
|
|
71
|
+
|
|
52
72
|
outline-offset: -4px;
|
|
53
73
|
outline-color: currentColor;
|
|
54
74
|
outline-width: 2px;
|
|
@@ -58,12 +78,13 @@ export default (name: string) => {
|
|
|
58
78
|
outline-style: solid;
|
|
59
79
|
}
|
|
60
80
|
|
|
81
|
+
/* We should probably be comfortable with showing these styles on non-hover devices. */
|
|
61
82
|
&:hover:not(:focus-visible) {
|
|
62
83
|
outline-style: dashed;
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
86
|
</style>
|
|
66
|
-
<button id="trigger" lang="en"
|
|
87
|
+
<button id="trigger" lang="en">á</button>
|
|
67
88
|
`;
|
|
68
89
|
|
|
69
90
|
return class extends HTMLElement implements AccentedTrigger {
|
|
@@ -75,6 +96,8 @@ export default (name: string) => {
|
|
|
75
96
|
|
|
76
97
|
#disposeOfVisibilityEffect: (() => void) | undefined;
|
|
77
98
|
|
|
99
|
+
#elementMutationObserver: MutationObserver | undefined;
|
|
100
|
+
|
|
78
101
|
element: Element | undefined;
|
|
79
102
|
|
|
80
103
|
dialog: AccentedDialog | undefined;
|
|
@@ -84,8 +107,8 @@ export default (name: string) => {
|
|
|
84
107
|
visible: Signal<boolean> | undefined;
|
|
85
108
|
|
|
86
109
|
constructor() {
|
|
110
|
+
super();
|
|
87
111
|
try {
|
|
88
|
-
super();
|
|
89
112
|
this.attachShadow({ mode: 'open' });
|
|
90
113
|
const content = template.content.cloneNode(true);
|
|
91
114
|
if (this.shadowRoot) {
|
|
@@ -104,50 +127,82 @@ export default (name: string) => {
|
|
|
104
127
|
if (trigger && this.element) {
|
|
105
128
|
trigger.ariaLabel = `Accessibility issues in ${this.element.nodeName.toLowerCase()}`;
|
|
106
129
|
}
|
|
107
|
-
|
|
108
|
-
|
|
130
|
+
|
|
131
|
+
this.#setTransform();
|
|
132
|
+
|
|
133
|
+
this.#elementMutationObserver = new MutationObserver(() => {
|
|
109
134
|
try {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// We append the dialog when the button is clicked,
|
|
113
|
-
// and remove it from the DOM when the dialog is closed.
|
|
114
|
-
// This gives us a performance improvement since Axe
|
|
115
|
-
// scan time seems to depend on the number of elements in the DOM.
|
|
116
|
-
if (this.dialog) {
|
|
117
|
-
this.#dialogCloseAbortController = new AbortController();
|
|
118
|
-
document.body.append(this.dialog);
|
|
119
|
-
this.dialog.showModal();
|
|
120
|
-
this.dialog.addEventListener('close', () => {
|
|
121
|
-
try {
|
|
122
|
-
this.dialog?.remove();
|
|
123
|
-
this.#dialogCloseAbortController?.abort();
|
|
124
|
-
} catch (error) {
|
|
125
|
-
logAndRethrow(error);
|
|
126
|
-
}
|
|
127
|
-
}, { signal: this.#dialogCloseAbortController.signal });
|
|
128
|
-
}
|
|
135
|
+
this.#setTransform();
|
|
129
136
|
} catch (error) {
|
|
130
137
|
logAndRethrow(error);
|
|
131
138
|
}
|
|
132
|
-
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (this.element) {
|
|
142
|
+
this.#elementMutationObserver.observe(this.element, {
|
|
143
|
+
attributes: true,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.#abortController = new AbortController();
|
|
148
|
+
trigger?.addEventListener(
|
|
149
|
+
'click',
|
|
150
|
+
(event) => {
|
|
151
|
+
try {
|
|
152
|
+
// event.preventDefault() ensures that if the issue is within a link,
|
|
153
|
+
// the link's default behavior (following the URL) is prevented.
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
|
|
156
|
+
// event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
|
|
157
|
+
// (a link, or a button, or anything else), it doesn't get triggered.
|
|
158
|
+
event.stopPropagation();
|
|
159
|
+
|
|
160
|
+
// We append the dialog when the button is clicked,
|
|
161
|
+
// and remove it from the DOM when the dialog is closed.
|
|
162
|
+
// This gives us a performance improvement since Axe
|
|
163
|
+
// scan time seems to depend on the number of elements in the DOM.
|
|
164
|
+
if (this.dialog) {
|
|
165
|
+
this.#dialogCloseAbortController = new AbortController();
|
|
166
|
+
document.body.append(this.dialog);
|
|
167
|
+
this.dialog.showModal();
|
|
168
|
+
this.dialog.addEventListener(
|
|
169
|
+
'close',
|
|
170
|
+
() => {
|
|
171
|
+
try {
|
|
172
|
+
this.dialog?.remove();
|
|
173
|
+
this.#dialogCloseAbortController?.abort();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
logAndRethrow(error);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{ signal: this.#dialogCloseAbortController.signal },
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logAndRethrow(error);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{ signal: this.#abortController.signal },
|
|
186
|
+
);
|
|
133
187
|
|
|
134
188
|
if (!supportsAnchorPositioning(window)) {
|
|
135
189
|
this.#disposeOfPositionEffect = effect(() => {
|
|
136
190
|
if (this.position && trigger) {
|
|
137
191
|
const position = this.position.value;
|
|
138
|
-
this.style.setProperty('top', `${position.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.style.setProperty('left', `${position.inlineEndLeft}px`, 'important');
|
|
143
|
-
}
|
|
192
|
+
this.style.setProperty('top', `${position.top}px`, 'important');
|
|
193
|
+
this.style.setProperty('left', `${position.left}px`, 'important');
|
|
194
|
+
this.style.setProperty('width', `${position.width}px`, 'important');
|
|
195
|
+
this.style.setProperty('height', `${position.height}px`, 'important');
|
|
144
196
|
}
|
|
145
197
|
});
|
|
146
|
-
|
|
147
|
-
this.#disposeOfVisibilityEffect = effect(() => {
|
|
148
|
-
this.style.setProperty('visibility', this.visible?.value ? 'visible' : 'hidden', 'important');
|
|
149
|
-
});
|
|
150
198
|
}
|
|
199
|
+
this.#disposeOfVisibilityEffect = effect(() => {
|
|
200
|
+
this.style.setProperty(
|
|
201
|
+
'visibility',
|
|
202
|
+
this.visible?.value ? 'visible' : 'hidden',
|
|
203
|
+
'important',
|
|
204
|
+
);
|
|
205
|
+
});
|
|
151
206
|
}
|
|
152
207
|
} catch (error) {
|
|
153
208
|
logAndRethrow(error);
|
|
@@ -159,7 +214,7 @@ export default (name: string) => {
|
|
|
159
214
|
if (this.#abortController) {
|
|
160
215
|
this.#abortController.abort();
|
|
161
216
|
}
|
|
162
|
-
if (this.#dialogCloseAbortController) {
|
|
217
|
+
if (this.#dialogCloseAbortController && !this.dialog?.open) {
|
|
163
218
|
this.#dialogCloseAbortController.abort();
|
|
164
219
|
this.dialog?.remove();
|
|
165
220
|
}
|
|
@@ -171,9 +226,26 @@ export default (name: string) => {
|
|
|
171
226
|
this.#disposeOfVisibilityEffect();
|
|
172
227
|
this.#disposeOfVisibilityEffect = undefined;
|
|
173
228
|
}
|
|
229
|
+
if (this.#elementMutationObserver) {
|
|
230
|
+
this.#elementMutationObserver.disconnect();
|
|
231
|
+
}
|
|
174
232
|
} catch (error) {
|
|
175
233
|
logAndRethrow(error);
|
|
176
234
|
}
|
|
177
235
|
}
|
|
236
|
+
|
|
237
|
+
#setTransform() {
|
|
238
|
+
// We read and write values in separate animation frames to avoid layout thrashing.
|
|
239
|
+
window.requestAnimationFrame(() => {
|
|
240
|
+
if (this.element) {
|
|
241
|
+
const transform = window.getComputedStyle(this.element).getPropertyValue('transform');
|
|
242
|
+
if (transform !== 'none') {
|
|
243
|
+
window.requestAnimationFrame(() => {
|
|
244
|
+
this.style.setProperty('transform', transform, 'important');
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
178
250
|
};
|
|
179
251
|
};
|