accented 0.0.0-20250223121749 → 0.0.0-20250404114312

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 (83) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +7 -3
  3. package/dist/accented.d.ts +2 -2
  4. package/dist/accented.d.ts.map +1 -1
  5. package/dist/accented.js +5 -2
  6. package/dist/accented.js.map +1 -1
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/constants.js +1 -0
  10. package/dist/constants.js.map +1 -1
  11. package/dist/dom-updater.d.ts.map +1 -1
  12. package/dist/dom-updater.js +38 -23
  13. package/dist/dom-updater.js.map +1 -1
  14. package/dist/elements/accented-dialog.d.ts.map +1 -1
  15. package/dist/elements/accented-dialog.js +60 -22
  16. package/dist/elements/accented-dialog.js.map +1 -1
  17. package/dist/elements/accented-trigger.d.ts +2 -0
  18. package/dist/elements/accented-trigger.d.ts.map +1 -1
  19. package/dist/elements/accented-trigger.js +64 -12
  20. package/dist/elements/accented-trigger.js.map +1 -1
  21. package/dist/fullscreen-listener.d.ts +2 -0
  22. package/dist/fullscreen-listener.d.ts.map +1 -0
  23. package/dist/fullscreen-listener.js +18 -0
  24. package/dist/fullscreen-listener.js.map +1 -0
  25. package/dist/scanner.d.ts.map +1 -1
  26. package/dist/scanner.js +15 -6
  27. package/dist/scanner.js.map +1 -1
  28. package/dist/state.d.ts +2 -1
  29. package/dist/state.d.ts.map +1 -1
  30. package/dist/state.js +3 -0
  31. package/dist/state.js.map +1 -1
  32. package/dist/types.d.ts +21 -8
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/utils/are-elements-with-issues-equal.d.ts +3 -0
  35. package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -0
  36. package/dist/utils/are-elements-with-issues-equal.js +5 -0
  37. package/dist/utils/are-elements-with-issues-equal.js.map +1 -0
  38. package/dist/utils/dom-helpers.d.ts +6 -0
  39. package/dist/utils/dom-helpers.d.ts.map +1 -0
  40. package/dist/utils/dom-helpers.js +19 -0
  41. package/dist/utils/dom-helpers.js.map +1 -0
  42. package/dist/utils/get-element-position.d.ts.map +1 -1
  43. package/dist/utils/get-element-position.js +53 -16
  44. package/dist/utils/get-element-position.js.map +1 -1
  45. package/dist/utils/get-parent.d.ts +2 -0
  46. package/dist/utils/get-parent.d.ts.map +1 -0
  47. package/dist/utils/get-parent.js +12 -0
  48. package/dist/utils/get-parent.js.map +1 -0
  49. package/dist/utils/get-scrollable-ancestors.d.ts +1 -1
  50. package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
  51. package/dist/utils/get-scrollable-ancestors.js +6 -2
  52. package/dist/utils/get-scrollable-ancestors.js.map +1 -1
  53. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +10 -0
  54. package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -0
  55. package/dist/utils/shadow-dom-aware-mutation-observer.js +64 -0
  56. package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -0
  57. package/dist/utils/transform-violations.d.ts +1 -1
  58. package/dist/utils/transform-violations.d.ts.map +1 -1
  59. package/dist/utils/transform-violations.js +18 -5
  60. package/dist/utils/transform-violations.js.map +1 -1
  61. package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
  62. package/dist/utils/update-elements-with-issues.js +9 -5
  63. package/dist/utils/update-elements-with-issues.js.map +1 -1
  64. package/package.json +4 -3
  65. package/src/accented.ts +5 -2
  66. package/src/constants.ts +1 -0
  67. package/src/dom-updater.ts +38 -22
  68. package/src/elements/accented-dialog.ts +60 -22
  69. package/src/elements/accented-trigger.ts +70 -12
  70. package/src/fullscreen-listener.ts +17 -0
  71. package/src/scanner.ts +17 -6
  72. package/src/state.ts +10 -2
  73. package/src/types.ts +23 -9
  74. package/src/utils/are-elements-with-issues-equal.ts +9 -0
  75. package/src/utils/dom-helpers.ts +22 -0
  76. package/src/utils/get-element-position.ts +54 -15
  77. package/src/utils/get-parent.ts +14 -0
  78. package/src/utils/get-scrollable-ancestors.ts +10 -5
  79. package/src/utils/shadow-dom-aware-mutation-observer.ts +78 -0
  80. package/src/utils/transform-violations.test.ts +10 -8
  81. package/src/utils/transform-violations.ts +20 -6
  82. package/src/utils/update-elements-with-issues.test.ts +46 -11
  83. package/src/utils/update-elements-with-issues.ts +10 -5
@@ -1 +1 @@
1
- {"version":3,"file":"transform-violations.js","sourceRoot":"","sources":["../../src/utils/transform-violations.ts"],"names":[],"mappings":"AAGA,SAAS,aAAa,CAAC,CAAc,EAAE,CAAc;IACnD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACvE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,UAAwC;IAClF,MAAM,kBAAkB,GAA6B,EAAE,CAAC;IAExD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACnC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YAEjC,4EAA4E;YAC5E,8EAA8E;YAC9E,2CAA2C;YAC3C,mGAAmG;YACnG,yEAAyE;YACzE,sHAAsH;YACtH,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAErC,kHAAkH;YAClH,kEAAkE;YAClE,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/C,IAAI,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC7C,MAAM,KAAK,GAAU;oBACnB,EAAE,EAAE,SAAS,CAAC,EAAE;oBAChB,KAAK,EAAE,SAAS,CAAC,IAAI;oBACrB,WAAW,EAAE,IAAI,CAAC,cAAc,IAAI,SAAS,CAAC,WAAW;oBACzD,GAAG,EAAE,SAAS,CAAC,OAAO;oBACtB,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI;iBACjC,CAAC;gBACF,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,CAAC,iBAAiB,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;gBACtH,IAAI,oBAAoB,KAAK,CAAC,CAAC,EAAE,CAAC;oBAChC,kBAAkB,CAAC,IAAI,CAAC;wBACtB,OAAO;wBACP,MAAM,EAAE,CAAC,KAAK,CAAC;qBAChB,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,kBAAkB,CAAC,oBAAoB,CAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC/D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,iBAAiB,IAAI,kBAAkB,EAAE,CAAC;QACnD,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,kBAAkB,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"transform-violations.js","sourceRoot":"","sources":["../../src/utils/transform-violations.ts"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,2FAA2F;AAC3F,MAAM,oCAAoC,GAAG;IAC3C,mBAAmB;IACnB,WAAW;IACX,iBAAiB;IACjB,6BAA6B;IAC7B,MAAM;IACN,oBAAoB;IACpB,6BAA6B,CAAC,oFAAoF;CACnH,CAAC;AAEF,SAAS,qBAAqB,CAAC,WAAmB,EAAE,OAAoB,EAAE,IAAY;IACpF,OAAO,oCAAoC,CAAC,QAAQ,CAAC,WAAW,CAAC;WAC5D,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,aAAa,CAAC,CAAc,EAAE,CAAc;IACnD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACvE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,UAAwC,EAAE,IAAY;IAChG,MAAM,kBAAkB,GAA6B,EAAE,CAAC;IAExD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACnC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YAEjC,4EAA4E;YAC5E,8EAA8E;YAC9E,2CAA2C;YAC3C,mGAAmG;YACnG,yEAAyE;YACzE,sHAAsH;YACtH,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAErC,IAAI,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;gBAClF,MAAM,KAAK,GAAU;oBACnB,EAAE,EAAE,SAAS,CAAC,EAAE;oBAChB,KAAK,EAAE,SAAS,CAAC,IAAI;oBACrB,WAAW,EAAE,IAAI,CAAC,cAAc,IAAI,SAAS,CAAC,WAAW;oBACzD,GAAG,EAAE,SAAS,CAAC,OAAO;oBACtB,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI;iBACjC,CAAC;gBACF,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,CAAC,iBAAiB,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;gBACtH,IAAI,oBAAoB,KAAK,CAAC,CAAC,EAAE,CAAC;oBAChC,kBAAkB,CAAC,IAAI,CAAC;wBACtB,OAAO;wBACP,QAAQ,EAAE,OAAO,CAAC,WAAW,EAAE;wBAC/B,MAAM,EAAE,CAAC,KAAK,CAAC;qBAChB,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,kBAAkB,CAAC,oBAAoB,CAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC/D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,iBAAiB,IAAI,kBAAkB,EAAE,CAAC;QACnD,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,kBAAkB,CAAC;AAC5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"update-elements-with-issues.d.ts","sourceRoot":"","sources":["../../src/utils/update-elements-with-issues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAW1D,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,0BAA0B,EAAE,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,OAAO,GAAG,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,QA4D/M"}
1
+ {"version":3,"file":"update-elements-with-issues.d.ts","sourceRoot":"","sources":["../../src/utils/update-elements-with-issues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAY1D,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,0BAA0B,EAAE,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,OAAO,GAAG,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,QAgE/M"}
@@ -1,29 +1,30 @@
1
1
  import { batch, signal } from '@preact/signals-core';
2
2
  import transformViolations from './transform-violations.js';
3
+ import areElementsWithIssuesEqual from './are-elements-with-issues-equal.js';
3
4
  import areIssueSetsEqual from './are-issue-sets-equal.js';
4
5
  import getElementPosition from './get-element-position.js';
5
6
  import getScrollableAncestors from './get-scrollable-ancestors.js';
6
7
  import supportsAnchorPositioning from './supports-anchor-positioning.js';
7
8
  let count = 0;
8
9
  export default function updateElementsWithIssues(extendedElementsWithIssues, violations, win, name) {
9
- const updatedElementsWithIssues = transformViolations(violations);
10
+ const updatedElementsWithIssues = transformViolations(violations, name);
10
11
  batch(() => {
11
12
  for (const updatedElementWithIssues of updatedElementsWithIssues) {
12
- const existingElementIndex = extendedElementsWithIssues.value.findIndex(extendedElementWithIssues => extendedElementWithIssues.element === updatedElementWithIssues.element);
13
+ const existingElementIndex = extendedElementsWithIssues.value.findIndex(extendedElementWithIssues => areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues));
13
14
  if (existingElementIndex > -1 && extendedElementsWithIssues.value[existingElementIndex] && !areIssueSetsEqual(extendedElementsWithIssues.value[existingElementIndex].issues.value, updatedElementWithIssues.issues)) {
14
15
  extendedElementsWithIssues.value[existingElementIndex].issues.value = updatedElementWithIssues.issues;
15
16
  }
16
17
  }
17
18
  const addedElementsWithIssues = updatedElementsWithIssues.filter(updatedElementWithIssues => {
18
- return !extendedElementsWithIssues.value.some(extendedElementWithIssues => extendedElementWithIssues.element === updatedElementWithIssues.element);
19
+ return !extendedElementsWithIssues.value.some(extendedElementWithIssues => areElementsWithIssuesEqual(extendedElementWithIssues, updatedElementWithIssues));
19
20
  });
20
21
  const removedElementsWithIssues = extendedElementsWithIssues.value.filter(extendedElementWithIssues => {
21
- return !updatedElementsWithIssues.some(updatedElementWithIssues => updatedElementWithIssues.element === extendedElementWithIssues.element);
22
+ return !updatedElementsWithIssues.some(updatedElementWithIssues => areElementsWithIssuesEqual(updatedElementWithIssues, extendedElementWithIssues));
22
23
  });
23
24
  if (addedElementsWithIssues.length > 0 || removedElementsWithIssues.length > 0) {
24
25
  extendedElementsWithIssues.value = [...extendedElementsWithIssues.value]
25
26
  .filter(extendedElementWithIssues => {
26
- return !removedElementsWithIssues.some(removedElementWithIssues => removedElementWithIssues.element === extendedElementWithIssues.element);
27
+ return !removedElementsWithIssues.some(removedElementWithIssues => areElementsWithIssuesEqual(removedElementWithIssues, extendedElementWithIssues));
27
28
  })
28
29
  .concat(addedElementsWithIssues
29
30
  .filter(addedElementWithIssues => addedElementWithIssues.element.isConnected)
@@ -51,9 +52,12 @@ export default function updateElementsWithIssues(extendedElementsWithIssues, vio
51
52
  return {
52
53
  id,
53
54
  element: addedElementWithIssues.element,
55
+ rootNode: addedElementWithIssues.rootNode,
54
56
  visible: trigger.visible,
55
57
  position: trigger.position,
56
58
  scrollableAncestors: signal(scrollableAncestors),
59
+ anchorNameValue: addedElementWithIssues.element.style.getPropertyValue('anchor-name')
60
+ || win.getComputedStyle(addedElementWithIssues.element).getPropertyValue('anchor-name'),
57
61
  trigger,
58
62
  issues
59
63
  };
@@ -1 +1 @@
1
- {"version":3,"file":"update-elements-with-issues.js","sourceRoot":"","sources":["../../src/utils/update-elements-with-issues.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-elements-with-issues.js","sourceRoot":"","sources":["../../src/utils/update-elements-with-issues.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "accented",
3
- "version": "0.0.0-20250223121749",
3
+ "version": "0.0.0-20250404114312",
4
4
  "description": "Continuous accessibility testing and issue highlighting for web development",
5
5
  "type": "module",
6
6
  "main": "dist/accented.js",
7
7
  "files": [
8
8
  "dist",
9
- "src"
9
+ "src",
10
+ "NOTICE"
10
11
  ],
11
12
  "repository": {
12
13
  "type": "git",
@@ -19,7 +20,7 @@
19
20
  "axe-core"
20
21
  ],
21
22
  "author": "Pavel Pomerantsev",
22
- "license": "MIT",
23
+ "license": "MIT AND MPL-2.0",
23
24
  "bugs": {
24
25
  "url": "https://github.com/pomerantsev/accented/issues"
25
26
  },
package/src/accented.ts CHANGED
@@ -5,6 +5,7 @@ import createLogger from './logger.js';
5
5
  import createScanner from './scanner.js';
6
6
  import setupScrollListeners from './scroll-listeners.js';
7
7
  import setupResizeListener from './resize-listener.js';
8
+ import setupFullscreenListener from './fullscreen-listener.js';
8
9
  import setupIntersectionObserver from './intersection-observer.js';
9
10
  import { enabled, extendedElementsWithIssues } from './state.js';
10
11
  import deepMerge from './utils/deep-merge.js';
@@ -34,9 +35,9 @@ export type { AccentedOptions, DisableAccented };
34
35
  * wait: 500,
35
36
  * leading: false
36
37
  * },
37
- * callback: ({ elementsWithIssues, scanDuration }) => {
38
+ * callback: ({ elementsWithIssues, performance }) => {
38
39
  * console.log('Elements with issues:', elementsWithIssues);
39
- * console.log('Scan duration:', scanDuration);
40
+ * console.log('Total blocking time:', performance.totalBlockingTime);
40
41
  * }
41
42
  * });
42
43
  */
@@ -95,6 +96,7 @@ export default function accented(options: AccentedOptions = {}): DisableAccented
95
96
  const cleanupLogger = output.console ? createLogger() : () => {};
96
97
  const cleanupScrollListeners = supportsAnchorPositioning(window) ? () => {} : setupScrollListeners();
97
98
  const cleanupResizeListener = supportsAnchorPositioning(window) ? () => {} : setupResizeListener();
99
+ const cleanupFullscreenListener = supportsAnchorPositioning(window) ? () => {} : setupFullscreenListener();
98
100
 
99
101
  return () => {
100
102
  try {
@@ -105,6 +107,7 @@ export default function accented(options: AccentedOptions = {}): DisableAccented
105
107
  cleanupLogger();
106
108
  cleanupScrollListeners();
107
109
  cleanupResizeListener();
110
+ cleanupFullscreenListener();
108
111
  if (cleanupIntersectionObserver) {
109
112
  cleanupIntersectionObserver();
110
113
  }
package/src/constants.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export const accentedUrl = 'https://www.npmjs.com/package/accented';
2
2
  export const issuesUrl = 'https://github.com/pomerantsev/accented/issues';
3
+ export const getAccentedElementNames = (name: string) => [`${name}-trigger`, `${name}-dialog`];
@@ -1,7 +1,10 @@
1
1
  import { effect } from '@preact/signals-core';
2
- import { extendedElementsWithIssues } from './state.js';
2
+ import { extendedElementsWithIssues, rootNodes } from './state.js';
3
3
  import type { ExtendedElementWithIssues } from './types';
4
+ import areElementsWithIssuesEqual from './utils/are-elements-with-issues-equal.js';
4
5
  import supportsAnchorPositioning from './utils/supports-anchor-positioning.js';
6
+ import { isDocument, isDocumentFragment, isShadowRoot } from './utils/dom-helpers.js';
7
+ import getParent from './utils/get-parent.js';
5
8
 
6
9
  export default function createDomUpdater(name: string, intersectionObserver?: IntersectionObserver) {
7
10
  const attrName = `data-${name}`;
@@ -13,8 +16,8 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
13
16
  .filter(anchorName => anchorName.startsWith('--'));
14
17
  }
15
18
 
16
- function setAnchorName (element: HTMLElement, id: number) {
17
- const anchorNameValue = element.style.getPropertyValue('anchor-name') || window.getComputedStyle(element).getPropertyValue('anchor-name');
19
+ function setAnchorName (elementWithIssues: ExtendedElementWithIssues) {
20
+ const { element, id, anchorNameValue } = elementWithIssues;
18
21
  const anchorNames = getAnchorNames(anchorNameValue);
19
22
  if (anchorNames.length > 0) {
20
23
  element.style.setProperty('anchor-name', `${anchorNameValue}, --${name}-anchor-${id}`);
@@ -23,14 +26,13 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
23
26
  }
24
27
  }
25
28
 
26
- function removeAnchorName (element: HTMLElement, id: number) {
27
- const anchorNameValue = element.style.getPropertyValue('anchor-name');
29
+ function removeAnchorName (elementWithIssues: ExtendedElementWithIssues) {
30
+ const { element, anchorNameValue } = elementWithIssues;
28
31
  const anchorNames = getAnchorNames(anchorNameValue);
29
- const index = anchorNames.indexOf(`--${name}-anchor-${id}`);
30
- if (anchorNames.length === 1 && index === 0) {
32
+ if (anchorNames.length > 0) {
33
+ element.style.setProperty('anchor-name', anchorNames.join(', '));
34
+ } else {
31
35
  element.style.removeProperty('anchor-name');
32
- } else if (anchorNames.length > 1 && index > -1) {
33
- element.style.setProperty('anchor-name', anchorNames.filter((_, i) => i !== index).join(', '));
34
36
  }
35
37
  }
36
38
 
@@ -38,10 +40,10 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
38
40
  for (const elementWithIssues of extendedElementsWithIssues) {
39
41
  elementWithIssues.element.setAttribute(attrName, elementWithIssues.id.toString());
40
42
  if (supportsAnchorPositioning(window)) {
41
- setAnchorName(elementWithIssues.element, elementWithIssues.id);
43
+ setAnchorName(elementWithIssues);
42
44
  }
43
45
 
44
- if (elementWithIssues.element.parentElement) {
46
+ if (getParent(elementWithIssues.element)) {
45
47
  elementWithIssues.element.insertAdjacentElement('afterend', elementWithIssues.trigger);
46
48
  } else {
47
49
  elementWithIssues.element.insertAdjacentElement('beforeend', elementWithIssues.trigger);
@@ -56,7 +58,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
56
58
  for (const elementWithIssues of extendedElementsWithIssues) {
57
59
  elementWithIssues.element.removeAttribute(attrName);
58
60
  if (supportsAnchorPositioning(window)) {
59
- removeAnchorName(elementWithIssues.element, elementWithIssues.id);
61
+ removeAnchorName(elementWithIssues);
60
62
  }
61
63
  elementWithIssues.trigger.remove();
62
64
  if (intersectionObserver) {
@@ -69,8 +71,10 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
69
71
  stylesheet.replaceSync(`
70
72
  @layer ${name} {
71
73
  :root {
72
- --${name}-primary-color: red;
73
- --${name}-secondary-color: white;
74
+ /* Ensure that the primary / secondary color combination meets WCAG 1.4.3 Contrast (Minimum) */
75
+ /* OKLCH stuff: https://oklch.com/ */
76
+ --${name}-primary-color: oklch(0.5 0.3 0);
77
+ --${name}-secondary-color: oklch(0.98 0 0);
74
78
  --${name}-outline-width: 2px;
75
79
  --${name}-outline-style: solid;
76
80
  }
@@ -86,19 +90,31 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
86
90
 
87
91
  let previousExtendedElementsWithIssues: Array<ExtendedElementWithIssues> = [];
88
92
 
89
- document.adoptedStyleSheets.push(stylesheet);
90
- const removeStylesheet = () => {
91
- if (document.adoptedStyleSheets.includes(stylesheet)) {
92
- document.adoptedStyleSheets.splice(document.adoptedStyleSheets.indexOf(stylesheet), 1);
93
+ let previousRootNodes: Set<Node> = new Set();
94
+
95
+ const disposeOfStyleSheetsEffect = effect(() => {
96
+ const newRootNodes = rootNodes.value;
97
+ const addedRootNodes = [...newRootNodes].filter(rootNode => !previousRootNodes.has(rootNode));
98
+ const removedRootNodes = [...previousRootNodes].filter(rootNode => !newRootNodes.has(rootNode));
99
+ for (const rootNode of addedRootNodes) {
100
+ if (isDocument(rootNode) || (isDocumentFragment(rootNode) && isShadowRoot(rootNode))) {
101
+ rootNode.adoptedStyleSheets.push(stylesheet);
102
+ }
93
103
  }
94
- };
104
+ for (const rootNode of removedRootNodes) {
105
+ if (isDocument(rootNode) || (isDocumentFragment(rootNode) && isShadowRoot(rootNode))) {
106
+ rootNode.adoptedStyleSheets.splice(rootNode.adoptedStyleSheets.indexOf(stylesheet), 1);
107
+ }
108
+ }
109
+ previousRootNodes = newRootNodes;
110
+ });
95
111
 
96
112
  const disposeOfElementsEffect = effect(() => {
97
113
  const added = extendedElementsWithIssues.value.filter(elementWithIssues => {
98
- return !previousExtendedElementsWithIssues.some(previousElementWithIssues => previousElementWithIssues.element === elementWithIssues.element);
114
+ return !previousExtendedElementsWithIssues.some(previousElementWithIssues => areElementsWithIssuesEqual(previousElementWithIssues, elementWithIssues));
99
115
  });
100
116
  const removed = previousExtendedElementsWithIssues.filter(previousElementWithIssues => {
101
- return !extendedElementsWithIssues.value.some(elementWithIssues => elementWithIssues.element === previousElementWithIssues.element);
117
+ return !extendedElementsWithIssues.value.some(elementWithIssues => areElementsWithIssuesEqual(elementWithIssues, previousElementWithIssues));
102
118
  });
103
119
  removeIssues(removed);
104
120
  setIssues(added);
@@ -106,7 +122,7 @@ export default function createDomUpdater(name: string, intersectionObserver?: In
106
122
  });
107
123
 
108
124
  return () => {
109
- removeStylesheet();
125
+ disposeOfStyleSheetsEffect();
110
126
  disposeOfElementsEffect();
111
127
  };
112
128
  }
@@ -56,20 +56,45 @@ export default () => {
56
56
  :host {
57
57
  all: initial !important;
58
58
 
59
- --light-color: white;
60
- --dark-color: black;
61
- --focus-color: #0078d4; /* Contrasts with both white and black. */
59
+ /* OKLCH stuff: https://oklch.com/ */
60
+ --light-color: oklch(0.98 0 0);
61
+ --dark-color: oklch(0.22 0 0);
62
+
63
+ --background-color: light-dark(var(--light-color), var(--dark-color));
64
+ --text-color: light-dark(var(--dark-color), var(--light-color));
65
+
66
+ --impact-lightness: 0.80;
67
+ --focus-lightness: 0.45;
68
+ @media (prefers-color-scheme: dark) {
69
+ --impact-lightness: 0.45;
70
+ --focus-lightness: 0.80;
71
+ }
72
+
73
+ --blue-hue: 230;
74
+ --gold-hue: 90;
75
+ --red-hue: 0;
76
+
77
+ /* Contrasts with background. */
78
+ --focus-color: oklch(var(--focus-lightness) 0.25 var(--blue-hue));
79
+
80
+ --impact-chroma: 0.16;
62
81
 
63
- --impact-minor-color: lightgray;
64
- --impact-moderate-color: gold;
65
- --impact-serious-color: #ff9e00;
66
- --impact-critical-color: #f883ec;
82
+ --impact-moderate-hue: var(--blue-hue);
83
+ --impact-serious-hue: var(--gold-hue);
84
+ --impact-critical-hue: var(--red-hue);
85
+
86
+ --impact-minor-color: oklch(var(--impact-lightness) 0 0);
87
+ --impact-moderate-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-moderate-hue));
88
+ --impact-serious-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-serious-hue));
89
+ --impact-critical-color: oklch(var(--impact-lightness) var(--impact-chroma) var(--impact-critical-hue));
90
+
91
+ --base-size: max(1rem, 16px);
67
92
 
68
93
  /* Spacing and typography custom props, inspired by https://utopia.fyi (simplified). */
69
94
 
70
95
  /* @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
96
  --ratio: 1.2;
72
- --step-0: 1rem;
97
+ --step-0: var(--base-size);
73
98
  --step-1: calc(var(--step-0) * var(--ratio));
74
99
  --step-2: calc(var(--step-1) * var(--ratio));
75
100
  --step-3: calc(var(--step-2) * var(--ratio));
@@ -77,15 +102,15 @@ export default () => {
77
102
  --step--1: calc(var(--step-0) / var(--ratio));
78
103
 
79
104
  /* @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.25rem;
81
- --space-2xs: 0.5rem;
82
- --space-xs: 0.75rem;
83
- --space-s: 1rem;
84
- --space-m: 1.5rem;
85
- --space-l: 2rem;
86
- --space-xl: 3rem;
87
- --space-2xl: 4rem;
88
- --space-3xl: 6rem;
105
+ --space-3xs: calc(0.25 * var(--base-size));
106
+ --space-2xs: calc(0.5 * var(--base-size));
107
+ --space-xs: calc(0.75 * var(--base-size));
108
+ --space-s: var(--base-size);
109
+ --space-m: calc(1.5 * var(--base-size));
110
+ --space-l: calc(2 * var(--base-size));
111
+ --space-xl: calc(3 * var(--base-size));
112
+ --space-2xl: calc(4 * var(--base-size));
113
+ --space-3xl: calc(6 * var(--base-size));
89
114
  }
90
115
 
91
116
  a[href], button {
@@ -116,12 +141,15 @@ export default () => {
116
141
  overflow-wrap: break-word;
117
142
  font-family: system-ui;
118
143
  line-height: 1.5;
119
- background-color: var(--light-color);
120
- color: var(--dark-color);
144
+ text-wrap: pretty;
145
+ background-color: var(--background-color);
146
+ color: var(--text-color);
121
147
  border: 2px solid currentColor;
122
148
  padding: var(--space-l);
123
149
  inline-size: min(90ch, calc(100% - var(--space-s)* 2));
124
150
  max-block-size: calc(100% - var(--space-s) * 2);
151
+
152
+ color-scheme: light dark;
125
153
  }
126
154
 
127
155
  #button-container {
@@ -129,8 +157,8 @@ export default () => {
129
157
  }
130
158
 
131
159
  #close {
132
- background-color: var(--light-color);
133
- color: var(--dark-color);
160
+ background-color: var(--background-color);
161
+ color: var(--text-color);
134
162
  border: 2px solid currentColor;
135
163
  padding-inline: var(--space-2xs);
136
164
  aspect-ratio: 1 / 1;
@@ -167,7 +195,7 @@ export default () => {
167
195
  }
168
196
 
169
197
  a {
170
- font-weight: bold;
198
+ font-weight: 500;
171
199
  }
172
200
  }
173
201
 
@@ -260,6 +288,16 @@ export default () => {
260
288
  }
261
289
  }, { signal: this.#abortController.signal });
262
290
 
291
+ dialog?.addEventListener('keydown', (event) => {
292
+ try {
293
+ if (event.key === 'Escape') {
294
+ event.stopPropagation();
295
+ }
296
+ } catch (error) {
297
+ logAndRethrow(error);
298
+ }
299
+ }, { signal: this.#abortController.signal });
300
+
263
301
  this.#disposeOfEffect = effect(() => {
264
302
  if (this.issues) {
265
303
  const issues = this.issues.value;
@@ -12,8 +12,6 @@ export interface AccentedTrigger extends HTMLElement {
12
12
  visible: Signal<boolean> | undefined;
13
13
  }
14
14
 
15
- const triggerSize = 'max(32px, 2rem)';
16
-
17
15
  // We want Accented to not throw an error in Node, and use static imports,
18
16
  // so we can't export `class extends HTMLElement` because HTMLElement is not available in Node.
19
17
  export default (name: string) => {
@@ -27,21 +25,37 @@ export default (name: string) => {
27
25
  template.innerHTML = `
28
26
  <style>
29
27
  :host {
28
+ --ratio: 1.2;
29
+ --base-size: max(1rem, 16px);
30
30
  position: fixed !important;
31
+ inset-inline-start: anchor(self-start) !important;
31
32
  inset-inline-end: anchor(self-end) !important;
32
33
  inset-block-start: anchor(self-start) !important;
34
+ inset-block-end: anchor(self-end) !important;
33
35
 
34
36
  position-visibility: anchors-visible !important;
35
37
 
36
38
  /* Revert potential effects of white-space: pre; set on a trigger's ancestor. */
37
39
  white-space: normal !important;
40
+
41
+ pointer-events: none !important;
38
42
  }
39
43
 
40
44
  #trigger {
45
+ pointer-events: auto;
46
+
47
+ position: absolute;
48
+ inset-block-start: 4px;
49
+ inset-inline-end: 4px;
50
+
41
51
  box-sizing: border-box;
42
- font-size: 1rem;
43
- inline-size: ${triggerSize};
44
- block-size: ${triggerSize};
52
+ font-size: calc(var(--ratio) * var(--ratio) * var(--base-size));
53
+ inline-size: calc(2 * var(--base-size));
54
+ block-size: calc(2 * var(--base-size));
55
+
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
45
59
 
46
60
  /* Make it look better in forced-colors mode. */
47
61
  border: 2px solid transparent;
@@ -49,6 +63,10 @@ export default (name: string) => {
49
63
  background-color: var(--${name}-primary-color);
50
64
  color: var(--${name}-secondary-color);
51
65
 
66
+ padding: 0;
67
+
68
+ border-radius: calc(0.25 * var(--base-size));
69
+
52
70
  outline-offset: -4px;
53
71
  outline-color: currentColor;
54
72
  outline-width: 2px;
@@ -63,7 +81,7 @@ export default (name: string) => {
63
81
  }
64
82
  }
65
83
  </style>
66
- <button id="trigger" lang="en">⚠</button>
84
+ <button id="trigger" lang="en">!</button>
67
85
  `;
68
86
 
69
87
  return class extends HTMLElement implements AccentedTrigger {
@@ -75,6 +93,8 @@ export default (name: string) => {
75
93
 
76
94
  #disposeOfVisibilityEffect: (() => void) | undefined;
77
95
 
96
+ #elementMutationObserver: MutationObserver | undefined;
97
+
78
98
  element: Element | undefined;
79
99
 
80
100
  dialog: AccentedDialog | undefined;
@@ -104,11 +124,34 @@ export default (name: string) => {
104
124
  if (trigger && this.element) {
105
125
  trigger.ariaLabel = `Accessibility issues in ${this.element.nodeName.toLowerCase()}`;
106
126
  }
127
+
128
+ this.#setTransform();
129
+
130
+ this.#elementMutationObserver = new MutationObserver(() => {
131
+ try {
132
+ this.#setTransform();
133
+ } catch (error) {
134
+ logAndRethrow(error);
135
+ }
136
+ });
137
+
138
+ if (this.element) {
139
+ this.#elementMutationObserver.observe(this.element, {
140
+ attributes: true
141
+ });
142
+ }
143
+
107
144
  this.#abortController = new AbortController();
108
145
  trigger?.addEventListener('click', (event) => {
109
146
  try {
147
+ // event.preventDefault() ensures that if the issue is within a link,
148
+ // the link's default behavior (following the URL) is prevented.
110
149
  event.preventDefault();
111
150
 
151
+ // event.stopPropagation() ensures that if there's a click handler on the trigger's ancestor
152
+ // (a link, or a button, or anything else), it doesn't get triggered.
153
+ event.stopPropagation();
154
+
112
155
  // We append the dialog when the button is clicked,
113
156
  // and remove it from the DOM when the dialog is closed.
114
157
  // This gives us a performance improvement since Axe
@@ -135,12 +178,10 @@ export default (name: string) => {
135
178
  this.#disposeOfPositionEffect = effect(() => {
136
179
  if (this.position && trigger) {
137
180
  const position = this.position.value;
138
- this.style.setProperty('top', `${position.blockStartTop}px`, 'important');
139
- if (position.direction === 'ltr') {
140
- this.style.setProperty('left', `calc(${position.inlineEndLeft}px - ${triggerSize})`, 'important');
141
- } else if (this.position.value.direction === 'rtl') {
142
- this.style.setProperty('left', `${position.inlineEndLeft}px`, 'important');
143
- }
181
+ this.style.setProperty('top', `${position.top}px`, 'important');
182
+ this.style.setProperty('left', `${position.left}px`, 'important');
183
+ this.style.setProperty('width', `${position.width}px`, 'important');
184
+ this.style.setProperty('height', `${position.height}px`, 'important');
144
185
  }
145
186
  });
146
187
 
@@ -171,9 +212,26 @@ export default (name: string) => {
171
212
  this.#disposeOfVisibilityEffect();
172
213
  this.#disposeOfVisibilityEffect = undefined;
173
214
  }
215
+ if (this.#elementMutationObserver) {
216
+ this.#elementMutationObserver.disconnect();
217
+ }
174
218
  } catch (error) {
175
219
  logAndRethrow(error);
176
220
  }
177
221
  }
222
+
223
+ #setTransform() {
224
+ // We read and write values in separate animation frames to avoid layout thrashing.
225
+ window.requestAnimationFrame(() => {
226
+ if (this.element) {
227
+ const transform = window.getComputedStyle(this.element).getPropertyValue('transform');
228
+ if (transform !== 'none') {
229
+ window.requestAnimationFrame(() => {
230
+ this.style.setProperty('transform', transform, 'important');
231
+ });
232
+ }
233
+ }
234
+ });
235
+ }
178
236
  };
179
237
  };
@@ -0,0 +1,17 @@
1
+ import logAndRethrow from './log-and-rethrow.js';
2
+ import recalculatePositions from './utils/recalculate-positions.js';
3
+
4
+ export default function setupResizeListener() {
5
+ const abortController = new AbortController();
6
+ window.addEventListener('fullscreenchange', () => {
7
+ try {
8
+ recalculatePositions();
9
+ } catch (error) {
10
+ logAndRethrow(error);
11
+ }
12
+ }, { signal: abortController.signal });
13
+
14
+ return () => {
15
+ abortController.abort();
16
+ };
17
+ };
package/src/scanner.ts CHANGED
@@ -6,8 +6,9 @@ import updateElementsWithIssues from './utils/update-elements-with-issues.js';
6
6
  import recalculatePositions from './utils/recalculate-positions.js';
7
7
  import recalculateScrollableAncestors from './utils/recalculate-scrollable-ancestors.js';
8
8
  import supportsAnchorPositioning from './utils/supports-anchor-positioning.js';
9
- import { issuesUrl } from './constants.js';
9
+ import { getAccentedElementNames, issuesUrl } from './constants.js';
10
10
  import logAndRethrow from './log-and-rethrow.js';
11
+ import createShadowDOMAwareMutationObserver from './utils/shadow-dom-aware-mutation-observer.js';
11
12
 
12
13
  export default function createScanner(name: string, axeContext: AxeContext, axeOptions: AxeOptions, throttle: Required<Throttle>, callback: Callback) {
13
14
  const axeRunningWindowProp = `__${name}_axe_running__`;
@@ -22,7 +23,7 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
22
23
 
23
24
  try {
24
25
 
25
- performance.mark('axe-start');
26
+ performance.mark('scan-start');
26
27
 
27
28
  win[axeRunningWindowProp] = true;
28
29
 
@@ -54,17 +55,27 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
54
55
  }
55
56
  win[axeRunningWindowProp] = false;
56
57
 
57
- const axeMeasure = performance.measure('axe', 'axe-start');
58
+ const scanMeasure = performance.measure('scan', 'scan-start');
59
+ const scanDuration = Math.round(scanMeasure.duration);
58
60
 
59
61
  if (!enabled.value) {
60
62
  return;
61
63
  }
62
64
 
65
+ performance.mark('dom-update-start');
66
+
63
67
  updateElementsWithIssues(extendedElementsWithIssues, result.violations, window, name);
64
68
 
69
+ const domUpdateMeasure = performance.measure('dom-update', 'dom-update-start');
70
+ const domUpdateDuration = Math.round(domUpdateMeasure.duration);
71
+
65
72
  callback({
66
73
  elementsWithIssues: elementsWithIssues.value,
67
- scanDuration: Math.round(axeMeasure.duration)
74
+ performance: {
75
+ totalBlockingTime: scanDuration + domUpdateDuration,
76
+ scan: scanDuration,
77
+ domUpdate: domUpdateDuration
78
+ }
68
79
  });
69
80
  } catch (error) {
70
81
  win[axeRunningWindowProp] = false;
@@ -77,8 +88,8 @@ export default function createScanner(name: string, axeContext: AxeContext, axeO
77
88
  // if that's an element or array of elements (not a selector).
78
89
  taskQueue.add(document);
79
90
 
80
- const accentedElementNames = [`${name}-trigger`, `${name}-dialog`];
81
- const mutationObserver = new MutationObserver(mutationList => {
91
+ const accentedElementNames = getAccentedElementNames(name);
92
+ const mutationObserver = createShadowDOMAwareMutationObserver(name, mutationList => {
82
93
  try {
83
94
  // We're not interested in mutations that are caused exclusively by the custom elements
84
95
  // introduced by Accented.