@motion-proto/live-tokens 0.96.0 → 0.98.2

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 (206) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +5 -0
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -3
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +71 -11
  4. package/CHANGELOG.md +309 -0
  5. package/README.md +2 -2
  6. package/bin/check-component.mjs +8 -0
  7. package/bin/check-page.mjs +6 -0
  8. package/bin/cli.mjs +36 -6
  9. package/bin/contractRunner.mjs +142 -17
  10. package/bin/lib/findings.mjs +20 -4
  11. package/bin/rules/a11y.mjs +53 -0
  12. package/bin/set-colors.mjs +1 -0
  13. package/bin/setup-claude.mjs +2 -2
  14. package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  15. package/dist-plugin/index.cjs +148 -3
  16. package/dist-plugin/index.js +250 -105
  17. package/dist-plugin/setColors/index.cjs +7 -1
  18. package/dist-plugin/setColors/index.d.cts +9 -4
  19. package/dist-plugin/setColors/index.d.ts +9 -4
  20. package/dist-plugin/setColors/index.js +8 -2
  21. package/dist-plugin/setGeometry/index.d.cts +1 -1
  22. package/dist-plugin/setGeometry/index.d.ts +1 -1
  23. package/dist-plugin/setType/index.d.cts +1 -1
  24. package/dist-plugin/setType/index.d.ts +1 -1
  25. package/dist-plugin/{themeTypes-B8_Idrp4.d.cts → themeTypes-BaTQUyac.d.cts} +10 -1
  26. package/dist-plugin/{themeTypes-B8_Idrp4.d.ts → themeTypes-BaTQUyac.d.ts} +10 -1
  27. package/package.json +49 -2
  28. package/src/demo/LabeledSelect.svelte +1 -1
  29. package/src/editor/bootstrap.ts +3 -1
  30. package/src/editor/component-editor/BadgeEditor.svelte +13 -0
  31. package/src/editor/component-editor/ButtonEditor.svelte +33 -2
  32. package/src/editor/component-editor/CalloutEditor.svelte +13 -0
  33. package/src/editor/component-editor/CardEditor.svelte +12 -2
  34. package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
  35. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
  36. package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
  37. package/src/editor/component-editor/DialogEditor.svelte +15 -0
  38. package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
  39. package/src/editor/component-editor/ImageEditor.svelte +20 -9
  40. package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
  41. package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
  42. package/src/editor/component-editor/InputEditor.svelte +23 -0
  43. package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
  44. package/src/editor/component-editor/NotificationEditor.svelte +15 -0
  45. package/src/editor/component-editor/PanelEditor.svelte +1 -1
  46. package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
  47. package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
  48. package/src/editor/component-editor/SectionDividerEditor.svelte +41 -13
  49. package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
  50. package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
  51. package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
  52. package/src/editor/component-editor/SliderEditor.svelte +33 -0
  53. package/src/editor/component-editor/TabBarEditor.svelte +23 -2
  54. package/src/editor/component-editor/TableEditor.svelte +14 -0
  55. package/src/editor/component-editor/ToggleEditor.svelte +17 -0
  56. package/src/editor/component-editor/TooltipEditor.svelte +11 -2
  57. package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
  58. package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
  59. package/src/editor/component-editor/registration.ts +2 -1
  60. package/src/editor/component-editor/registry.ts +69 -27
  61. package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
  62. package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
  63. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
  64. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -10
  65. package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
  66. package/src/editor/core/a11y/a11yReport.ts +101 -0
  67. package/src/editor/core/a11y/a11yReportTypes.ts +69 -0
  68. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  69. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  70. package/src/editor/core/palettes/contrast.ts +44 -0
  71. package/src/editor/core/palettes/contrastPairs.ts +79 -0
  72. package/src/editor/core/palettes/resolveColor.ts +77 -0
  73. package/src/editor/core/store/editorPersistence.ts +9 -1
  74. package/src/editor/core/store/editorRenderer.ts +2 -0
  75. package/src/editor/core/store/editorStore.ts +29 -0
  76. package/src/editor/core/store/editorTypes.ts +9 -0
  77. package/src/editor/core/store/editorViewStore.ts +2 -2
  78. package/src/editor/core/store/unsavedEdits.ts +10 -0
  79. package/src/editor/core/themes/buildColors.ts +16 -5
  80. package/src/editor/core/themes/liveStateStream.ts +7 -1
  81. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  82. package/src/editor/core/themes/slices/customColors.ts +226 -0
  83. package/src/editor/core/themes/themeTypes.ts +10 -0
  84. package/src/editor/docs/chapters.ts +1 -0
  85. package/src/editor/docs/content/accessibility.md +362 -0
  86. package/src/editor/docs/content/editing-tokens.md +56 -1
  87. package/src/editor/docs/content.generated.ts +2 -1
  88. package/src/editor/pages/ComponentEditorPage.svelte +10 -1
  89. package/src/editor/pages/EditorShell.svelte +97 -10
  90. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  91. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  92. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  93. package/src/editor/ui/CustomColorReplaceStep.svelte +118 -0
  94. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  95. package/src/editor/ui/GradientEditor.svelte +9 -1
  96. package/src/editor/ui/ThemePanel.svelte +4 -4
  97. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  98. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  99. package/src/editor/ui/UIPaletteSelector.svelte +347 -15
  100. package/src/editor/ui/UITokenSelector.svelte +33 -14
  101. package/src/editor/ui/VariablesTab.svelte +3 -0
  102. package/src/editor/ui/a11y/A11yComponentIssues.svelte +103 -0
  103. package/src/editor/ui/a11y/A11yFindingRow.svelte +57 -0
  104. package/src/editor/ui/a11y/A11yIssueRow.svelte +121 -0
  105. package/src/editor/ui/a11y/A11yOverview.svelte +242 -0
  106. package/src/editor/ui/a11y/A11yPanel.svelte +128 -0
  107. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  108. package/src/editor/ui/a11y/A11yRailMark.svelte +34 -0
  109. package/src/editor/ui/a11y/AccessibilityTab.svelte +334 -0
  110. package/src/editor/ui/a11y/a11yView.ts +218 -0
  111. package/src/editor/ui/anchorToTrigger.ts +63 -0
  112. package/src/editor/ui/contrastReadout.ts +45 -0
  113. package/src/editor/ui/sections/CustomColorsSection.svelte +254 -0
  114. package/src/live-tokens/data/themes/autumn.json +20 -20
  115. package/src/live-tokens/data/themes/halloween.json +20 -20
  116. package/src/live-tokens/data/themes/midnight-study.json +20 -20
  117. package/src/live-tokens/data/themes/ocean.json +20 -20
  118. package/src/live-tokens/data/themes/royal-velvet.json +20 -20
  119. package/src/live-tokens/data/themes/sketchy.json +20 -20
  120. package/src/live-tokens/data/themes/spring-meadow.json +20 -20
  121. package/src/live-tokens/data/themes/sunset.json +20 -20
  122. package/src/system/components/Badge.svelte +3 -3
  123. package/src/system/components/Button.svelte +9 -3
  124. package/src/system/components/CodeSnippet.svelte +8 -1
  125. package/src/system/components/CornerBadge.svelte +3 -3
  126. package/src/system/components/Dialog.svelte +43 -14
  127. package/src/system/components/IconButton.svelte +1 -1
  128. package/src/system/components/Image.svelte +7 -0
  129. package/src/system/components/ImageLightbox.svelte +26 -26
  130. package/src/system/components/Input.svelte +8 -1
  131. package/src/system/components/MenuSelect.svelte +30 -2
  132. package/src/system/components/Notification.svelte +1 -1
  133. package/src/system/components/ProgressBar.svelte +6 -0
  134. package/src/system/components/RadioButton.svelte +36 -3
  135. package/src/system/components/SegmentedControl.svelte +36 -2
  136. package/src/system/components/SlidePager.svelte +2 -2
  137. package/src/system/components/Slider.svelte +13 -9
  138. package/src/system/components/TabBar.svelte +33 -3
  139. package/src/system/components/Toggle.svelte +2 -2
  140. package/src/system/components/VideoFrame.svelte +1 -1
  141. package/src/system/components/VideoLightbox.svelte +2 -2
  142. package/src/system/internal/focusTrap.ts +35 -0
  143. package/src/testing-js/__aria__/badge.yml +40 -0
  144. package/src/testing-js/__aria__/button.yml +88 -0
  145. package/src/testing-js/__aria__/callout.yml +12 -0
  146. package/src/testing-js/__aria__/card.yml +8 -0
  147. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  148. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  149. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  150. package/src/testing-js/__aria__/dialog.yml +42 -0
  151. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  152. package/src/testing-js/__aria__/image.yml +10 -0
  153. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  154. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  155. package/src/testing-js/__aria__/input.yml +61 -0
  156. package/src/testing-js/__aria__/menuselect.yml +30 -0
  157. package/src/testing-js/__aria__/notification.yml +30 -0
  158. package/src/testing-js/__aria__/panel.yml +2 -0
  159. package/src/testing-js/__aria__/progressbar.yml +2 -0
  160. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  161. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  162. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  163. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  164. package/src/testing-js/__aria__/slidepager.yml +72 -0
  165. package/src/testing-js/__aria__/slider.yml +28 -0
  166. package/src/testing-js/__aria__/tabbar.yml +35 -0
  167. package/src/testing-js/__aria__/table.yml +144 -0
  168. package/src/testing-js/__aria__/toggle.yml +12 -0
  169. package/src/testing-js/__aria__/tooltip.yml +2 -0
  170. package/src/testing-js/__aria__/videoframe.yml +6 -0
  171. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  172. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  173. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-5HNZHIHG.js} +211 -14
  174. package/src/testing-js/chunk-5HNZHIHG.js.map +1 -0
  175. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  176. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  177. package/src/testing-js/chunk-EWSGE27V.js +168 -0
  178. package/src/testing-js/chunk-EWSGE27V.js.map +1 -0
  179. package/src/testing-js/{chunk-GKO27NGN.js → chunk-R54I7YL6.js} +2 -2
  180. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-RWH46IU5.js} +97 -29
  181. package/src/testing-js/chunk-RWH46IU5.js.map +1 -0
  182. package/src/testing-js/chunk-SHLPDNAQ.js +658 -0
  183. package/src/testing-js/chunk-SHLPDNAQ.js.map +1 -0
  184. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  185. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  186. package/src/testing-js/component-a11y.contract.js +283 -0
  187. package/src/testing-js/component-a11y.contract.js.map +1 -0
  188. package/src/testing-js/component-alias.contract.js +2 -2
  189. package/src/testing-js/component-behavior.contract.js +2 -2
  190. package/src/testing-js/component-editor.contract.js +6 -5
  191. package/src/testing-js/component-editor.contract.js.map +1 -1
  192. package/src/testing-js/component-render.contract.js +10 -6
  193. package/src/testing-js/component-render.contract.js.map +1 -1
  194. package/src/testing-js/index.d.ts +44 -16
  195. package/src/testing-js/index.js +9 -9
  196. package/src/testing-js/page-compliance.contract.js +163 -18
  197. package/src/testing-js/page-compliance.contract.js.map +1 -1
  198. package/src/testing-js/vitest.js +3 -3
  199. package/template/package.json +9 -0
  200. package/src/editor/ui/keepInViewport.ts +0 -84
  201. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  202. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  203. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  204. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  205. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  206. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-R54I7YL6.js.map} +0 -0
@@ -1,14 +1,26 @@
1
1
  import {
2
- isInapplicable
3
- } from "./chunk-3UKGXCDL.js";
2
+ MOTION_MS,
3
+ MOTION_URL,
4
+ TEXT_SPACING_RULE,
5
+ TEXT_SPACING_URL,
6
+ newUnderTextSpacing
7
+ } from "./chunk-SHLPDNAQ.js";
8
+ import {
9
+ missingEngine,
10
+ recordA11y,
11
+ scanAxe
12
+ } from "./chunk-EWSGE27V.js";
4
13
  import {
5
14
  DEFAULT_PAGE_VIEWPORTS,
6
15
  PAGES_ENV,
7
16
  PAGE_VIEWPORTS_ENV
8
- } from "./chunk-NB3NZRBM.js";
17
+ } from "./chunk-7CM5D7PF.js";
18
+ import {
19
+ isInapplicable
20
+ } from "./chunk-YVVR3GJZ.js";
9
21
  import {
10
22
  allContracts
11
- } from "./chunk-SHBYZUPD.js";
23
+ } from "./chunk-5HNZHIHG.js";
12
24
 
13
25
  // src/testing/page-compliance.contract.ts
14
26
  import { test } from "@playwright/test";
@@ -258,6 +270,12 @@ var TEXT_STYLES = [
258
270
  // src/editor/core/palettes/contrast.ts
259
271
  var AA_BODY = 4.5;
260
272
  var AA_LARGE = 3;
273
+ var LARGE_TEXT_PX = 24;
274
+ var LARGE_BOLD_PX = 18.66;
275
+ var BOLD = 700;
276
+ function isLargeText(px, weight) {
277
+ return px >= LARGE_TEXT_PX || px >= LARGE_BOLD_PX && weight >= BOLD;
278
+ }
261
279
  function linearizeChannel(v8) {
262
280
  const s = v8 / 255;
263
281
  return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
@@ -311,6 +329,23 @@ function variantPattern(label) {
311
329
  const keys = [.../* @__PURE__ */ new Set([words.join("-"), ...words])].map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
312
330
  return `(?:^|-)(?:${keys.join("|")})$`;
313
331
  }
332
+ function stateMarkers(contract) {
333
+ if (isInapplicable(contract.states)) return [];
334
+ const markers = [];
335
+ for (const state of contract.states) {
336
+ if (restingState(state.state)) continue;
337
+ const attributes = [];
338
+ for (const [part, attrs] of Object.entries(state.attributes ?? {})) {
339
+ const declared = contract.parts[part];
340
+ if (declared === void 0 || typeof declared !== "string" && declared.portal) continue;
341
+ const selector = part === contract.root ? null : typeof declared === "string" ? declared : declared.selector;
342
+ for (const [name, value] of Object.entries(attrs)) attributes.push({ selector, name, value });
343
+ }
344
+ if (!state.forceClass && !attributes.some((a) => a.value !== null)) continue;
345
+ markers.push({ forceClass: state.forceClass ?? null, attributes });
346
+ }
347
+ return markers;
348
+ }
314
349
  function paintChecks(contract, map) {
315
350
  const checks = [];
316
351
  for (const [part, properties] of Object.entries(map)) {
@@ -354,7 +389,7 @@ function restingPaintSpec(contract) {
354
389
  }
355
390
  }
356
391
  if (entries.length === 0) return null;
357
- return { id: contract.id, root: rootSelector, entries };
392
+ return { id: contract.id, root: rootSelector, entries, away: stateMarkers(contract) };
358
393
  }
359
394
  var TEXT_AXES = ["fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing"];
360
395
  var BLOCK_DISPLAYS = ["block", "flow-root", "list-item", "flex", "grid", "table-cell", "table-caption"];
@@ -362,9 +397,6 @@ var GRID_TOLERANCE = 1;
362
397
  var GRID_VIEWPORT = 768;
363
398
  var SETTLE_QUIET_MS = 300;
364
399
  var SETTLE_BUDGET_MS = 5e3;
365
- var LARGE_TEXT_PX = 24;
366
- var LARGE_BOLD_PX = 18.66;
367
- var BOLD = 700;
368
400
  var cssName = (camel) => camel.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
369
401
  function report(failures, scope, describe) {
370
402
  const lines = failures.map(describe);
@@ -376,8 +408,7 @@ function hex(rgb) {
376
408
  return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
377
409
  }
378
410
  function aaFloor(pair) {
379
- const large = pair.fontSize >= LARGE_TEXT_PX || pair.fontSize >= LARGE_BOLD_PX && pair.fontWeight >= BOLD;
380
- return large ? AA_LARGE : AA_BODY;
411
+ return isLargeText(pair.fontSize, pair.fontWeight) ? AA_LARGE : AA_BODY;
381
412
  }
382
413
  function reasons(skipped) {
383
414
  return Object.entries(skipped).map(([reason, count]) => `${reason} (${count})`).join(", ");
@@ -394,7 +425,11 @@ var PageHarness = class _PageHarness {
394
425
  static async open(page, target, viewport) {
395
426
  await installPageProbe(page);
396
427
  await page.setViewportSize(viewport);
397
- await page.goto(target.route);
428
+ await _PageHarness.load(page, target.route);
429
+ return new _PageHarness(page, target, viewport);
430
+ }
431
+ static async load(page, route) {
432
+ await page.goto(route);
398
433
  await page.locator(PAGE_CONTAINER).waitFor();
399
434
  await page.waitForFunction((selectors) => {
400
435
  const container = document.querySelector(selectors.container);
@@ -403,7 +438,6 @@ var PageHarness = class _PageHarness {
403
438
  }, { container: PAGE_CONTAINER, chrome: CHROME });
404
439
  await page.waitForLoadState("networkidle");
405
440
  await _PageHarness.settle(page);
406
- return new _PageHarness(page, target, viewport);
407
441
  }
408
442
  /**
409
443
  * Every rule here measures geometry, and the geometry moves for as long as
@@ -523,24 +557,33 @@ var PageHarness = class _PageHarness {
523
557
  let asserted = 0;
524
558
  const root = document.querySelector(container);
525
559
  if (!root) return { instances, asserted, failures, uncovered };
526
- const normalize = (node, css, raw) => {
560
+ const normalize = (node, css, raw, rendered = false) => {
527
561
  const probe = document.createElement("div");
528
- probe.style.display = "none";
562
+ if (rendered) {
563
+ probe.style.visibility = "hidden";
564
+ probe.style.position = getComputedStyle(node).position;
565
+ } else probe.style.display = "none";
529
566
  probe.style.borderStyle = "solid";
530
567
  probe.style.outlineStyle = "solid";
531
568
  probe.style.fontSize = getComputedStyle(node).fontSize;
532
569
  (node.parentElement ?? document.body).appendChild(probe);
533
570
  probe.style.setProperty(css.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`), raw);
534
- const computed = getComputedStyle(probe)[css];
571
+ const computed = (getComputedStyle(probe)[css] ?? "").trim();
535
572
  probe.remove();
536
- return (computed ?? "").trim();
573
+ return !rendered && computed.endsWith("%") ? normalize(node, css, raw, true) : computed;
537
574
  };
575
+ const atRest = (element, markers) => !markers.some((marker) => (marker.forceClass === null || element.classList.contains(marker.forceClass)) && marker.attributes.every(({ selector, name, value }) => {
576
+ const part = selector === null ? element : element.querySelector(selector);
577
+ if (!part) return false;
578
+ return value === null ? !part.hasAttribute(name) : part.getAttribute(name) === value;
579
+ }));
538
580
  for (const spec of list) {
539
581
  const patterns = spec.entries.map((entry) => entry.pattern === null ? null : new RegExp(entry.pattern));
540
582
  const variants = [...new Set(spec.entries.map((entry) => entry.variant).filter((v) => v !== null))];
541
583
  for (const element of root.querySelectorAll(spec.root)) {
542
584
  if (element.closest(chrome)) continue;
543
585
  instances++;
586
+ if (!atRest(element, spec.away)) continue;
544
587
  const classes = [...element.classList];
545
588
  const line = window.__liveTokensPageLine(element, source);
546
589
  let covered = false;
@@ -842,13 +885,14 @@ var PageHarness = class _PageHarness {
842
885
  });
843
886
  }
844
887
  }
845
- const clips = (style) => style.overflowX !== "visible" || style.overflowY !== "visible";
846
888
  for (const spec of list) {
847
889
  for (const instance of root.querySelectorAll(spec.root)) {
848
890
  if (instance.closest(chrome)) continue;
849
891
  let clipper = null;
850
892
  for (let node = instance.parentElement; node; node = node.parentElement) {
851
- if (clips(getComputedStyle(node))) {
893
+ const overflowX = getComputedStyle(node).overflowX;
894
+ if (overflowX === "auto" || overflowX === "scroll") break;
895
+ if (overflowX !== "visible") {
852
896
  clipper = node;
853
897
  break;
854
898
  }
@@ -957,6 +1001,91 @@ var PageHarness = class _PageHarness {
957
1001
  }
958
1002
  return null;
959
1003
  }
1004
+ /**
1005
+ * The accessibility rules return their results. The suite passes them to
1006
+ * `recordA11y`, and the run reports each one at `warn` (decision 3 of
1007
+ * docs/plans/a11y-testing.md).
1008
+ *
1009
+ * axe over the page's own markup. The router's chrome and every shipped
1010
+ * component root stay out, since the component suite answers for what a
1011
+ * component renders, and `color-contrast` stays off, since `page-contrast`
1012
+ * owns page contrast (decision 8).
1013
+ */
1014
+ async scanA11y(componentRoots2) {
1015
+ const results = await scanAxe(this.page, PAGE_CONTAINER, [CHROME, ...componentRoots2], "a11y-page");
1016
+ const lines = await this.page.evaluate(({ targets: targets2, source }) => targets2.map((target) => {
1017
+ try {
1018
+ const element = document.querySelector(target);
1019
+ return element ? window.__liveTokensPageLine(element, source) : 1;
1020
+ } catch {
1021
+ return 1;
1022
+ }
1023
+ }), { targets: results.map((result) => result.target), source: this.target.source });
1024
+ return results.map((result, index) => ({ ...result, line: lines[index] }));
1025
+ }
1026
+ /** The overflow observation again under the SC 1.4.12 override, minus every
1027
+ * failure the page already has without it, which `page-overflow` reports. */
1028
+ async scanTextSpacing(specs) {
1029
+ const found = await newUnderTextSpacing(
1030
+ this.page,
1031
+ async () => (await this.observeOverflow(specs)).failures,
1032
+ (failure) => `${failure.kind}|${failure.line}|${failure.tag}`
1033
+ );
1034
+ return found.map((failure) => ({
1035
+ rule: "a11y-text-spacing",
1036
+ engineRule: TEXT_SPACING_RULE,
1037
+ impact: null,
1038
+ target: failure.kind === "instance" ? failure.tag : `<${failure.tag}>`,
1039
+ html: "",
1040
+ help: `Under the text spacing override, ${failure.detail}`,
1041
+ helpUrl: TEXT_SPACING_URL,
1042
+ line: failure.line
1043
+ }));
1044
+ }
1045
+ /**
1046
+ * Loads the page again under `prefers-reduced-motion: reduce`, so a script
1047
+ * that reads the preference at mount sees it too, and reports every
1048
+ * animation still running whose run lasts past `MOTION_MS`. The page stays
1049
+ * under the emulation afterwards.
1050
+ */
1051
+ async scanReducedMotion() {
1052
+ await this.page.emulateMedia({ reducedMotion: "reduce" });
1053
+ await _PageHarness.load(this.page, this.target.route);
1054
+ return this.page.evaluate(({ container, chrome, source, limit, url }) => {
1055
+ const root = document.querySelector(container);
1056
+ if (!root) return [];
1057
+ const results = [];
1058
+ const seen = /* @__PURE__ */ new Set();
1059
+ for (const animation of document.getAnimations()) {
1060
+ if (animation.playState !== "running") continue;
1061
+ const effect = animation.effect;
1062
+ if (!(effect instanceof KeyframeEffect) || !effect.target) continue;
1063
+ const element = effect.target;
1064
+ if (!root.contains(element) || element.closest(chrome)) continue;
1065
+ const end = Number(effect.getComputedTiming().endTime ?? 0);
1066
+ if (end <= limit) continue;
1067
+ const name = animation instanceof CSSAnimation ? `The animation ${animation.animationName.replace(/^svelte-[a-z0-9]+-/, "")}` : animation instanceof CSSTransition ? `A transition of ${animation.transitionProperty}` : `A script animation${animation.id ? ` ${animation.id}` : ""}`;
1068
+ const classes = [...element.classList].filter((c) => !/^svelte-[a-z0-9]+$/.test(c));
1069
+ const target = `${element.tagName.toLowerCase()}${classes.map((c) => `.${c}`).join("")}` + (effect.pseudoElement ?? "");
1070
+ if (seen.has(`${target}
1071
+ ${name}`)) continue;
1072
+ seen.add(`${target}
1073
+ ${name}`);
1074
+ const html = element.outerHTML;
1075
+ results.push({
1076
+ rule: "a11y-reduced-motion",
1077
+ engineRule: "prefers-reduced-motion",
1078
+ impact: null,
1079
+ target,
1080
+ html: html.slice(0, html.indexOf(">") + 1),
1081
+ help: `${name} keeps running under prefers-reduced-motion: reduce, ` + (Number.isFinite(end) ? `for ${Math.round(end)} ms` : "without end"),
1082
+ helpUrl: url,
1083
+ line: window.__liveTokensPageLine(element, source)
1084
+ });
1085
+ }
1086
+ return results;
1087
+ }, { container: PAGE_CONTAINER, chrome: CHROME, source: this.target.source, limit: MOTION_MS, url: MOTION_URL });
1088
+ }
960
1089
  };
961
1090
  async function installPageProbe(page) {
962
1091
  await page.addInitScript(() => {
@@ -1015,6 +1144,22 @@ for (const target of targets) {
1015
1144
  const inapplicable = await harness.assertOverflow(specs);
1016
1145
  test.skip(inapplicable !== null, inapplicable ?? "");
1017
1146
  });
1147
+ test(`page-a11y | ${target.source} | ${at}`, async ({ page }) => {
1148
+ const missing = await missingEngine(["axe"]);
1149
+ test.skip(missing !== null, missing ?? "");
1150
+ const roots = await componentRoots();
1151
+ const harness = await PageHarness.open(page, target, viewport);
1152
+ recordA11y(await harness.scanA11y(roots));
1153
+ });
1154
+ test(`page-text-spacing | ${target.source} | ${at}`, async ({ page }) => {
1155
+ const specs = await paintSpecs();
1156
+ const harness = await PageHarness.open(page, target, viewport);
1157
+ recordA11y(await harness.scanTextSpacing(specs));
1158
+ });
1159
+ test(`page-reduced-motion | ${target.source} | ${at}`, async ({ page }) => {
1160
+ const harness = await PageHarness.open(page, target, viewport);
1161
+ recordA11y(await harness.scanReducedMotion());
1162
+ });
1018
1163
  }
1019
1164
  }
1020
1165
  //# sourceMappingURL=page-compliance.contract.js.map