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.
Files changed (207) hide show
  1. package/README.md +44 -193
  2. package/dist/accented.d.ts +7 -7
  3. package/dist/accented.d.ts.map +1 -1
  4. package/dist/accented.js +30 -27
  5. package/dist/accented.js.map +1 -1
  6. package/dist/common/tokens.d.ts +2 -0
  7. package/dist/common/tokens.d.ts.map +1 -0
  8. package/dist/common/tokens.js +2 -0
  9. package/dist/common/tokens.js.map +1 -0
  10. package/dist/constants.d.ts +1 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/constants.js.map +1 -1
  14. package/dist/dom-updater.d.ts +1 -1
  15. package/dist/dom-updater.d.ts.map +1 -1
  16. package/dist/dom-updater.js +14 -13
  17. package/dist/dom-updater.js.map +1 -1
  18. package/dist/elements/accented-dialog.d.ts +2 -3
  19. package/dist/elements/accented-dialog.d.ts.map +1 -1
  20. package/dist/elements/accented-dialog.js +14 -8
  21. package/dist/elements/accented-dialog.js.map +1 -1
  22. package/dist/elements/accented-trigger.d.ts +3 -4
  23. package/dist/elements/accented-trigger.d.ts.map +1 -1
  24. package/dist/elements/accented-trigger.js +10 -10
  25. package/dist/elements/accented-trigger.js.map +1 -1
  26. package/dist/fullscreen-listener.d.ts +1 -1
  27. package/dist/fullscreen-listener.d.ts.map +1 -1
  28. package/dist/fullscreen-listener.js +3 -4
  29. package/dist/fullscreen-listener.js.map +1 -1
  30. package/dist/intersection-observer.d.ts +1 -1
  31. package/dist/intersection-observer.d.ts.map +1 -1
  32. package/dist/intersection-observer.js +12 -6
  33. package/dist/intersection-observer.js.map +1 -1
  34. package/dist/log-and-rethrow.d.ts +1 -1
  35. package/dist/log-and-rethrow.d.ts.map +1 -1
  36. package/dist/log-and-rethrow.js +2 -3
  37. package/dist/log-and-rethrow.js.map +1 -1
  38. package/dist/logger.d.ts +1 -1
  39. package/dist/logger.d.ts.map +1 -1
  40. package/dist/logger.js +2 -2
  41. package/dist/logger.js.map +1 -1
  42. package/dist/register-elements.d.ts +1 -1
  43. package/dist/register-elements.d.ts.map +1 -1
  44. package/dist/register-elements.js +6 -7
  45. package/dist/register-elements.js.map +1 -1
  46. package/dist/resize-listener.d.ts +1 -1
  47. package/dist/resize-listener.d.ts.map +1 -1
  48. package/dist/resize-listener.js +3 -4
  49. package/dist/resize-listener.js.map +1 -1
  50. package/dist/scanner.d.ts +2 -2
  51. package/dist/scanner.d.ts.map +1 -1
  52. package/dist/scanner.js +36 -36
  53. package/dist/scanner.js.map +1 -1
  54. package/dist/scroll-listeners.d.ts +1 -1
  55. package/dist/scroll-listeners.d.ts.map +1 -1
  56. package/dist/scroll-listeners.js +3 -4
  57. package/dist/scroll-listeners.js.map +1 -1
  58. package/dist/state.d.ts +1 -1
  59. package/dist/state.d.ts.map +1 -1
  60. package/dist/state.js +4 -5
  61. package/dist/state.js.map +1 -1
  62. package/dist/task-queue.d.ts +2 -2
  63. package/dist/task-queue.d.ts.map +1 -1
  64. package/dist/task-queue.js +1 -1
  65. package/dist/task-queue.js.map +1 -1
  66. package/dist/types.d.ts +102 -46
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js.map +1 -1
  69. package/dist/utils/are-elements-with-issues-equal.d.ts +2 -2
  70. package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -1
  71. package/dist/utils/are-elements-with-issues-equal.js +3 -3
  72. package/dist/utils/are-elements-with-issues-equal.js.map +1 -1
  73. package/dist/utils/are-issue-sets-equal.d.ts +2 -2
  74. package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
  75. package/dist/utils/are-issue-sets-equal.js +3 -3
  76. package/dist/utils/are-issue-sets-equal.js.map +1 -1
  77. package/dist/utils/containing-blocks.d.ts.map +1 -1
  78. package/dist/utils/containing-blocks.js +1 -1
  79. package/dist/utils/containing-blocks.js.map +1 -1
  80. package/dist/utils/contains.d.ts +1 -1
  81. package/dist/utils/contains.d.ts.map +1 -1
  82. package/dist/utils/contains.js +1 -1
  83. package/dist/utils/contains.js.map +1 -1
  84. package/dist/utils/deduplicate-nodes.js +0 -1
  85. package/dist/utils/deduplicate-nodes.js.map +1 -1
  86. package/dist/utils/deep-merge.d.ts +1 -1
  87. package/dist/utils/deep-merge.d.ts.map +1 -1
  88. package/dist/utils/deep-merge.js +8 -5
  89. package/dist/utils/deep-merge.js.map +1 -1
  90. package/dist/utils/dom-helpers.d.ts.map +1 -1
  91. package/dist/utils/dom-helpers.js +4 -2
  92. package/dist/utils/dom-helpers.js.map +1 -1
  93. package/dist/utils/ensure-non-empty.d.ts +1 -1
  94. package/dist/utils/ensure-non-empty.d.ts.map +1 -1
  95. package/dist/utils/ensure-non-empty.js +2 -2
  96. package/dist/utils/ensure-non-empty.js.map +1 -1
  97. package/dist/utils/get-element-html.d.ts +1 -1
  98. package/dist/utils/get-element-html.d.ts.map +1 -1
  99. package/dist/utils/get-element-html.js +4 -2
  100. package/dist/utils/get-element-html.js.map +1 -1
  101. package/dist/utils/get-element-position.d.ts +2 -2
  102. package/dist/utils/get-element-position.d.ts.map +1 -1
  103. package/dist/utils/get-element-position.js +21 -25
  104. package/dist/utils/get-element-position.js.map +1 -1
  105. package/dist/utils/get-parent.d.ts +1 -1
  106. package/dist/utils/get-parent.d.ts.map +1 -1
  107. package/dist/utils/get-parent.js +1 -1
  108. package/dist/utils/get-parent.js.map +1 -1
  109. package/dist/utils/get-scan-context.d.ts +2 -2
  110. package/dist/utils/get-scan-context.d.ts.map +1 -1
  111. package/dist/utils/get-scan-context.js +9 -9
  112. package/dist/utils/get-scan-context.js.map +1 -1
  113. package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
  114. package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
  115. package/dist/utils/get-scrollable-ancestors.js +5 -5
  116. package/dist/utils/get-scrollable-ancestors.js.map +1 -1
  117. package/dist/utils/is-node-in-scan-context.d.ts +2 -2
  118. package/dist/utils/is-node-in-scan-context.d.ts.map +1 -1
  119. package/dist/utils/is-node-in-scan-context.js +5 -5
  120. package/dist/utils/is-node-in-scan-context.js.map +1 -1
  121. package/dist/utils/is-non-empty.d.ts +2 -0
  122. package/dist/utils/is-non-empty.d.ts.map +1 -0
  123. package/dist/utils/is-non-empty.js +4 -0
  124. package/dist/utils/is-non-empty.js.map +1 -0
  125. package/dist/utils/normalize-context.d.ts +2 -2
  126. package/dist/utils/normalize-context.d.ts.map +1 -1
  127. package/dist/utils/normalize-context.js +10 -8
  128. package/dist/utils/normalize-context.js.map +1 -1
  129. package/dist/utils/recalculate-positions.d.ts +1 -1
  130. package/dist/utils/recalculate-positions.d.ts.map +1 -1
  131. package/dist/utils/recalculate-positions.js +5 -5
  132. package/dist/utils/recalculate-positions.js.map +1 -1
  133. package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
  134. package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
  135. package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
  136. package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
  137. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +1 -1
  138. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -1
  139. package/dist/utils/shadow-dom-aware-mutation-observer.js +19 -22
  140. package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -1
  141. package/dist/utils/supports-anchor-positioning.d.ts +1 -1
  142. package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
  143. package/dist/utils/supports-anchor-positioning.js +1 -1
  144. package/dist/utils/supports-anchor-positioning.js.map +1 -1
  145. package/dist/utils/transform-violations.d.ts +2 -2
  146. package/dist/utils/transform-violations.d.ts.map +1 -1
  147. package/dist/utils/transform-violations.js +9 -9
  148. package/dist/utils/transform-violations.js.map +1 -1
  149. package/dist/utils/update-elements-with-issues.d.ts +3 -3
  150. package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
  151. package/dist/utils/update-elements-with-issues.js +34 -29
  152. package/dist/utils/update-elements-with-issues.js.map +1 -1
  153. package/dist/validate-options.d.ts +2 -2
  154. package/dist/validate-options.d.ts.map +1 -1
  155. package/dist/validate-options.js +24 -23
  156. package/dist/validate-options.js.map +1 -1
  157. package/package.json +7 -4
  158. package/src/accented.test.ts +2 -2
  159. package/src/accented.ts +39 -32
  160. package/src/common/tokens.ts +1 -0
  161. package/src/constants.ts +1 -1
  162. package/src/dom-updater.ts +26 -19
  163. package/src/elements/accented-dialog.ts +69 -43
  164. package/src/elements/accented-trigger.ts +54 -43
  165. package/src/fullscreen-listener.ts +15 -11
  166. package/src/intersection-observer.ts +27 -16
  167. package/src/log-and-rethrow.ts +2 -3
  168. package/src/logger.ts +8 -6
  169. package/src/register-elements.ts +7 -7
  170. package/src/resize-listener.ts +15 -11
  171. package/src/scanner.ts +66 -50
  172. package/src/scroll-listeners.ts +27 -19
  173. package/src/state.ts +24 -21
  174. package/src/task-queue.test.ts +5 -4
  175. package/src/task-queue.ts +2 -2
  176. package/src/types.ts +151 -95
  177. package/src/utils/are-elements-with-issues-equal.ts +7 -5
  178. package/src/utils/are-issue-sets-equal.test.ts +10 -6
  179. package/src/utils/are-issue-sets-equal.ts +8 -6
  180. package/src/utils/containing-blocks.ts +6 -3
  181. package/src/utils/contains.test.ts +2 -2
  182. package/src/utils/contains.ts +1 -1
  183. package/src/utils/deduplicate-nodes.ts +1 -1
  184. package/src/utils/deep-merge.test.ts +8 -1
  185. package/src/utils/deep-merge.ts +14 -8
  186. package/src/utils/dom-helpers.ts +6 -2
  187. package/src/utils/ensure-non-empty.ts +2 -2
  188. package/src/utils/get-element-html.ts +4 -2
  189. package/src/utils/get-element-position.ts +37 -24
  190. package/src/utils/get-parent.ts +1 -1
  191. package/src/utils/get-scan-context.test.ts +14 -8
  192. package/src/utils/get-scan-context.ts +12 -15
  193. package/src/utils/get-scrollable-ancestors.ts +8 -5
  194. package/src/utils/is-node-in-scan-context.test.ts +3 -3
  195. package/src/utils/is-node-in-scan-context.ts +6 -6
  196. package/src/utils/is-non-empty.ts +3 -0
  197. package/src/utils/normalize-context.test.ts +9 -9
  198. package/src/utils/normalize-context.ts +17 -10
  199. package/src/utils/recalculate-positions.ts +5 -5
  200. package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
  201. package/src/utils/shadow-dom-aware-mutation-observer.ts +21 -24
  202. package/src/utils/supports-anchor-positioning.ts +3 -3
  203. package/src/utils/transform-violations.test.ts +22 -20
  204. package/src/utils/transform-violations.ts +14 -10
  205. package/src/utils/update-elements-with-issues.test.ts +49 -49
  206. package/src/utils/update-elements-with-issues.ts +96 -71
  207. package/src/validate-options.ts +91 -38
package/src/constants.ts CHANGED
@@ -1,3 +1,3 @@
1
- export const accentedUrl = 'https://www.npmjs.com/package/accented';
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`];
@@ -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 default function createDomUpdater(name: string, intersectionObserver?: IntersectionObserver) {
34
+ export function createDomUpdater(name: string, intersectionObserver?: IntersectionObserver) {
34
35
  const attrName = `data-${name}`;
35
36
 
36
- function getAnchorNames (anchorNameValue: string) {
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 (elementWithIssues: ExtendedElementWithIssues) {
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 (elementWithIssues: ExtendedElementWithIssues) {
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 (extendedElementsWithIssues: Array<ExtendedElementWithIssues>) {
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 (extendedElementsWithIssues: Array<ExtendedElementWithIssues>) {
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: oklch(0.5 0.3 0);
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(rootNode => !newRootNodes.has(rootNode));
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 => areElementsWithIssuesEqual(previousElementWithIssues, elementWithIssues));
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 => areElementsWithIssuesEqual(elementWithIssues, previousElementWithIssues));
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 default () => {
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: boolean = false;
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('click', () => {
274
- try {
275
- dialog?.close();
276
- } catch (error) {
277
- logAndRethrow(error);
278
- }
279
- }, { signal: this.#abortController.signal });
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
- } catch (error) {
295
- logAndRethrow(error);
296
- }
297
- }, { signal: this.#abortController.signal });
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 + ' (' + issue.id + ')';
323
+ title.textContent = `${issue.title} (${issue.id})`;
311
324
  title.href = issue.url;
312
325
 
313
- impact.textContent = 'User impact: ' + issue.impact;
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 (descriptionTitle && descriptionList && descriptionItems.length > 1) {
321
- descriptionTitle.textContent = descriptionItems[0]!;
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('close', () => {
343
- try {
344
- this.open = false;
345
- this.dispatchEvent(new Event('close'));
346
- } catch (error) {
347
- logAndRethrow(error);
348
- }
349
- }, { signal: this.#abortController.signal });
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 (!dialog || typeof dialog.getBoundingClientRect !== 'function' || typeof dialog.close !== 'function') {
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 supportsAnchorPositioning from '../utils/supports-anchor-positioning.js';
6
- import logAndRethrow from '../log-and-rethrow.js';
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 default (name: string) => {
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('click', (event) => {
147
- try {
148
- // event.preventDefault() ensures that if the issue is within a link,
149
- // the link's default behavior (following the URL) is prevented.
150
- event.preventDefault();
151
-
152
- // event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
153
- // (a link, or a button, or anything else), it doesn't get triggered.
154
- event.stopPropagation();
155
-
156
- // We append the dialog when the button is clicked,
157
- // and remove it from the DOM when the dialog is closed.
158
- // This gives us a performance improvement since Axe
159
- // scan time seems to depend on the number of elements in the DOM.
160
- if (this.dialog) {
161
- this.#dialogCloseAbortController = new AbortController();
162
- document.body.append(this.dialog);
163
- this.dialog.showModal();
164
- this.dialog.addEventListener('close', () => {
165
- try {
166
- this.dialog?.remove();
167
- this.#dialogCloseAbortController?.abort();
168
- } catch (error) {
169
- logAndRethrow(error);
170
- }
171
- }, { signal: this.#dialogCloseAbortController.signal });
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
- } catch (error) {
174
- logAndRethrow(error);
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 default function setupResizeListener() {
4
+ export function setupResizeListener() {
5
5
  const abortController = new AbortController();
6
- window.addEventListener('fullscreenchange', () => {
7
- try {
8
- recalculatePositions();
9
- } catch (error) {
10
- logAndRethrow(error);
11
- }
12
- }, { signal: abortController.signal });
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 default function setupIntersectionObserver() {
6
- const intersectionObserver = new IntersectionObserver((entries) => {
7
- try {
8
- for (const entry of entries) {
9
- const extendedElementWithIssues = extendedElementsWithIssues.value.find(el => el.element === entry.target);
10
- if (extendedElementWithIssues) {
11
- extendedElementWithIssues.visible.value = entry.isIntersecting;
12
- if (entry.isIntersecting) {
13
- extendedElementWithIssues.position.value = getElementPosition(entry.target, window);
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
- } catch (error) {
18
- logAndRethrow(error);
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
  }
@@ -1,9 +1,8 @@
1
1
  import { issuesUrl } from './constants.js';
2
2
 
3
- export default function logAndRethrow(error: unknown) {
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 type { ElementWithIssues } from './types';
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 default function createLogger() {
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((acc, { issues }) => acc + issues.length, 0);
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) {
@@ -1,16 +1,16 @@
1
- import getAccentedTrigger from './elements/accented-trigger.js';
2
- import getAccentedDialog from './elements/accented-dialog.js';
1
+ import { getAccentedDialog } from './elements/accented-dialog.js';
2
+ import { getAccentedTrigger } from './elements/accented-trigger.js';
3
3
 
4
- export default function registerElements(name: string): void {
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
+ }
@@ -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 default function setupResizeListener() {
4
+ export function setupResizeListener() {
5
5
  const abortController = new AbortController();
6
- window.addEventListener('resize', () => {
7
- try {
8
- recalculatePositions();
9
- } catch (error) {
10
- logAndRethrow(error);
11
- }
12
- }, { signal: abortController.signal });
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
+ }