@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
@@ -1098,7 +1098,13 @@ function buildColors(input, carry = {}) {
1098
1098
  for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
1099
1099
  Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
1100
1100
  return {
1101
- colors: { editorConfigs: configs, harmonyAxes, gradients, cssVariables },
1101
+ colors: {
1102
+ editorConfigs: configs,
1103
+ harmonyAxes,
1104
+ gradients,
1105
+ ...carry.customColors ? { customColors: carry.customColors } : {},
1106
+ cssVariables
1107
+ },
1102
1108
  report: {
1103
1109
  ...report,
1104
1110
  gradients: gradients === carry.gradients ? "carried" : "recipes",
@@ -1,4 +1,4 @@
1
- import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch, d as HarmonyMode } from '../themeTypes-B8_Idrp4.cjs';
1
+ import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.cjs';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
3
3
  export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
4
4
 
@@ -44,12 +44,14 @@ interface ColorsInput {
44
44
  canvasGradient?: boolean;
45
45
  }
46
46
  /** The live colors and type this pass reads before it replaces the color
47
- * state: the override bag, so every name no palette owns rides through, and
48
- * the swatch gradients, so user-tuned ones survive. Shadow opacity is
49
- * derived, not carried — see `shadowVars`. */
47
+ * state: the override bag, so every name no palette owns rides through, the
48
+ * swatch gradients, so user-tuned ones survive, and the custom colours, which
49
+ * ride through untouched. Shadow opacity is derived, not carried — see
50
+ * `shadowVars`. */
50
51
  interface CarryForward {
51
52
  cssVariables?: Record<string, string>;
52
53
  gradients?: GradientDiskToken[];
54
+ customColors?: CustomColorDiskToken[];
53
55
  }
54
56
  interface ContrastCheck {
55
57
  textVar: string;
@@ -80,6 +82,9 @@ interface ColorsState {
80
82
  editorConfigs: Record<string, PaletteConfig>;
81
83
  harmonyAxes: HarmonyAxis[];
82
84
  gradients: GradientDiskToken[];
85
+ /** Present only when the carried colours and type held the field, so a
86
+ * recolor adds no key to a theme that never had one. */
87
+ customColors?: CustomColorDiskToken[];
83
88
  cssVariables: Record<string, string>;
84
89
  }
85
90
  interface BuildColorsResult {
@@ -1,4 +1,4 @@
1
- import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch, d as HarmonyMode } from '../themeTypes-B8_Idrp4.js';
1
+ import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.js';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
3
3
  export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
4
4
 
@@ -44,12 +44,14 @@ interface ColorsInput {
44
44
  canvasGradient?: boolean;
45
45
  }
46
46
  /** The live colors and type this pass reads before it replaces the color
47
- * state: the override bag, so every name no palette owns rides through, and
48
- * the swatch gradients, so user-tuned ones survive. Shadow opacity is
49
- * derived, not carried — see `shadowVars`. */
47
+ * state: the override bag, so every name no palette owns rides through, the
48
+ * swatch gradients, so user-tuned ones survive, and the custom colours, which
49
+ * ride through untouched. Shadow opacity is derived, not carried — see
50
+ * `shadowVars`. */
50
51
  interface CarryForward {
51
52
  cssVariables?: Record<string, string>;
52
53
  gradients?: GradientDiskToken[];
54
+ customColors?: CustomColorDiskToken[];
53
55
  }
54
56
  interface ContrastCheck {
55
57
  textVar: string;
@@ -80,6 +82,9 @@ interface ColorsState {
80
82
  editorConfigs: Record<string, PaletteConfig>;
81
83
  harmonyAxes: HarmonyAxis[];
82
84
  gradients: GradientDiskToken[];
85
+ /** Present only when the carried colours and type held the field, so a
86
+ * recolor adds no key to a theme that never had one. */
87
+ customColors?: CustomColorDiskToken[];
83
88
  cssVariables: Record<string, string>;
84
89
  }
85
90
  interface BuildColorsResult {
@@ -15,7 +15,7 @@ import {
15
15
  scaleToCssVar,
16
16
  setCurveAnchor,
17
17
  syncBaseAnchor
18
- } from "../chunk-A4TBP4MM.js";
18
+ } from "../chunk-SLICH4IH.js";
19
19
  import {
20
20
  CURRENT_COMPONENT_SCHEMA_VERSION
21
21
  } from "../chunk-QQASYPW7.js";
@@ -483,7 +483,13 @@ function buildColors(input, carry = {}) {
483
483
  for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
484
484
  Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
485
485
  return {
486
- colors: { editorConfigs: configs, harmonyAxes, gradients, cssVariables },
486
+ colors: {
487
+ editorConfigs: configs,
488
+ harmonyAxes,
489
+ gradients,
490
+ ...carry.customColors ? { customColors: carry.customColors } : {},
491
+ cssVariables
492
+ },
487
493
  report: {
488
494
  ...report,
489
495
  gradients: gradients === carry.gradients ? "carried" : "recipes",
@@ -1,4 +1,4 @@
1
- import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-B8_Idrp4.cjs';
1
+ import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.cjs';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
3
3
  export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-B8_Idrp4.js';
1
+ import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.js';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
3
3
  export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { c as FontStackVariable, C as ColorsAndType, b as FontStack, F as FontSource } from '../themeTypes-B8_Idrp4.cjs';
1
+ import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.cjs';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
3
3
 
4
4
  type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
@@ -1,4 +1,4 @@
1
- import { c as FontStackVariable, C as ColorsAndType, b as FontStack, F as FontSource } from '../themeTypes-B8_Idrp4.js';
1
+ import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.js';
2
2
  export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
3
3
 
4
4
  type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
@@ -181,6 +181,12 @@ interface GradientDiskToken {
181
181
  opacity?: number;
182
182
  }[];
183
183
  }
184
+ /** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
185
+ * holds its own OKLCH value, one value for light and dark alike. */
186
+ interface CustomColorDiskToken {
187
+ variable: string;
188
+ color: Oklch;
189
+ }
184
190
  /**
185
191
  * Where a live component config resolved from: the unsaved `_working`
186
192
  * buffer, or the open theme's embedded copy. Every theme carries every
@@ -208,6 +214,9 @@ interface ColorsAndType {
208
214
  /** Absent on files saved before gradients round-tripped; the loader keeps
209
215
  * the stock defaults then, which match what those files rendered. */
210
216
  gradients?: GradientDiskToken[];
217
+ /** Absent when the theme holds no custom colour; the loader reads that as
218
+ * an empty list. */
219
+ customColors?: CustomColorDiskToken[];
211
220
  /** Four fixed harmony axes, each owning a hue with an optional bound family. */
212
221
  harmonyAxes?: HarmonyAxis[];
213
222
  /**
@@ -257,4 +266,4 @@ interface ComponentConfig {
257
266
  schemaVersion?: number;
258
267
  }
259
268
 
260
- export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, FontStack as b, FontStackVariable as c, HarmonyMode as d };
269
+ export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
@@ -181,6 +181,12 @@ interface GradientDiskToken {
181
181
  opacity?: number;
182
182
  }[];
183
183
  }
184
+ /** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
185
+ * holds its own OKLCH value, one value for light and dark alike. */
186
+ interface CustomColorDiskToken {
187
+ variable: string;
188
+ color: Oklch;
189
+ }
184
190
  /**
185
191
  * Where a live component config resolved from: the unsaved `_working`
186
192
  * buffer, or the open theme's embedded copy. Every theme carries every
@@ -208,6 +214,9 @@ interface ColorsAndType {
208
214
  /** Absent on files saved before gradients round-tripped; the loader keeps
209
215
  * the stock defaults then, which match what those files rendered. */
210
216
  gradients?: GradientDiskToken[];
217
+ /** Absent when the theme holds no custom colour; the loader reads that as
218
+ * an empty list. */
219
+ customColors?: CustomColorDiskToken[];
211
220
  /** Four fixed harmony axes, each owning a hue with an optional bound family. */
212
221
  harmonyAxes?: HarmonyAxis[];
213
222
  /**
@@ -257,4 +266,4 @@ interface ComponentConfig {
257
266
  schemaVersion?: number;
258
267
  }
259
268
 
260
- export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, FontStack as b, FontStackVariable as c, HarmonyMode as d };
269
+ export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.96.0",
3
+ "version": "0.98.2",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -56,6 +56,7 @@
56
56
  "dist-plugin",
57
57
  "src/testing-js",
58
58
  ".claude/skills",
59
+ "!.claude/skills/evaluate",
59
60
  "!.claude/skills/**/skill-alt.md",
60
61
  "bin",
61
62
  "template",
@@ -204,24 +205,61 @@
204
205
  "check:smoke-install": "bash scripts/smoke-install.sh",
205
206
  "check:smoke-create": "bash scripts/smoke-create.sh",
206
207
  "eval": "bash scripts/eval.sh",
208
+ "eval:billing": "bash scripts/eval-harbor.sh",
207
209
  "build:eval-project": "bash scripts/build-eval-project.sh",
208
210
  "check:smoke-component-tests": "bash scripts/smoke-component-tests.sh",
209
211
  "check:smoke-page-tests": "bash scripts/smoke-page-tests.sh",
210
212
  "prepublishOnly": "npm run check:no-style-imports && npm run check:no-tooling-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:pages && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:token-contract && npm run check:preset-themes && npm run check:skills && npm run check:cli-strings && npm run check:skill-sources && npm run check:skill-atlas && npm run check:smoke-install && npm run check:smoke-create && npm run check:smoke-component-tests && npm run check:smoke-page-tests"
211
213
  },
212
214
  "peerDependencies": {
215
+ "@axe-core/playwright": "^4.11.1",
213
216
  "@playwright/test": "^1.55",
217
+ "@siteimprove/alfa-act": "^0.119.0",
218
+ "@siteimprove/alfa-device": "^0.119.0",
219
+ "@siteimprove/alfa-dom": "^0.119.0",
220
+ "@siteimprove/alfa-http": "^0.119.0",
221
+ "@siteimprove/alfa-rules": "^0.119.0",
222
+ "@siteimprove/alfa-url": "^0.119.0",
223
+ "@siteimprove/alfa-web": "^0.119.0",
214
224
  "@sveltejs/vite-plugin-svelte": "^7.0",
225
+ "axe-core": "^4.11.1",
215
226
  "happy-dom": "^20.0",
216
227
  "sass": "^1.0",
217
228
  "svelte": "^5",
218
229
  "vite": "^8",
219
- "vitest": "^4.0"
230
+ "vitest": "^4.0 || ^5.0"
220
231
  },
221
232
  "peerDependenciesMeta": {
233
+ "@axe-core/playwright": {
234
+ "optional": true
235
+ },
222
236
  "@playwright/test": {
223
237
  "optional": true
224
238
  },
239
+ "@siteimprove/alfa-act": {
240
+ "optional": true
241
+ },
242
+ "@siteimprove/alfa-device": {
243
+ "optional": true
244
+ },
245
+ "@siteimprove/alfa-dom": {
246
+ "optional": true
247
+ },
248
+ "@siteimprove/alfa-http": {
249
+ "optional": true
250
+ },
251
+ "@siteimprove/alfa-rules": {
252
+ "optional": true
253
+ },
254
+ "@siteimprove/alfa-url": {
255
+ "optional": true
256
+ },
257
+ "@siteimprove/alfa-web": {
258
+ "optional": true
259
+ },
260
+ "axe-core": {
261
+ "optional": true
262
+ },
225
263
  "happy-dom": {
226
264
  "optional": true
227
265
  },
@@ -230,9 +268,18 @@
230
268
  }
231
269
  },
232
270
  "devDependencies": {
271
+ "@axe-core/playwright": "^4.14.0",
233
272
  "@playwright/test": "^1.62.1",
273
+ "@siteimprove/alfa-act": "^0.119.0",
274
+ "@siteimprove/alfa-device": "^0.119.0",
275
+ "@siteimprove/alfa-dom": "^0.119.0",
276
+ "@siteimprove/alfa-http": "^0.119.0",
277
+ "@siteimprove/alfa-rules": "^0.119.0",
278
+ "@siteimprove/alfa-url": "^0.119.0",
279
+ "@siteimprove/alfa-web": "^0.119.0",
234
280
  "@sveltejs/vite-plugin-svelte": "^7.1.2",
235
281
  "@types/node": "^25.9.1",
282
+ "axe-core": "^4.14.0",
236
283
  "happy-dom": "^20.9.0",
237
284
  "sass": "^1.98.0",
238
285
  "svelte": "^5.55.5",
@@ -164,7 +164,7 @@
164
164
 
165
165
  {#if open}
166
166
  <div class="labeled-select-panel" bind:this={panelEl} use:portal>
167
- <MenuSelect {items} {value} minWidth="12rem" onchange={select} />
167
+ <MenuSelect {items} {value} ariaLabel={label} minWidth="12rem" onchange={select} />
168
168
  </div>
169
169
  {/if}
170
170
 
@@ -50,16 +50,18 @@ export async function bootLiveTokens(
50
50
  if (import.meta.env.DEV) {
51
51
  // Imported here, never statically, so a build drops the editor. The
52
52
  // registry alone imports every shipped component and editor.
53
- const [editorStore, themeDocumentSync, liveStateStream, { initializeTheme }] = await Promise.all([
53
+ const [editorStore, themeDocumentSync, liveStateStream, a11yReport, { initializeTheme }] = await Promise.all([
54
54
  import('./core/store/editorStore'),
55
55
  import('./core/themes/themeDocumentSync'),
56
56
  import('./core/themes/liveStateStream'),
57
+ import('./core/a11y/a11yReport'),
57
58
  import('./core/themes/themeInit'),
58
59
  import('./component-editor/registry'),
59
60
  ]);
60
61
  editorStore.init();
61
62
  themeDocumentSync.init();
62
63
  liveStateStream.init();
64
+ void a11yReport.init();
63
65
  if (opts.components) {
64
66
  for (const entry of opts.components) {
65
67
  registerComponent(entry);
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import type { Token } from './scaffolding/types';
3
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
3
4
  import { badgeVariants } from '../../system/components/Badge.svelte';
4
5
 
5
6
  export const component = 'badge';
@@ -57,6 +58,18 @@
57
58
  );
58
59
 
59
60
  const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
61
+
62
+ function variantContrastPairs(v: Variant): ContrastPair[] {
63
+ return [{
64
+ foreground: `--badge-${v}-text`,
65
+ backdrop: [`--badge-${v}-surface`],
66
+ role: 'text',
67
+ fontSize: `--badge-${v}-text-font-size`,
68
+ fontWeight: `--badge-${v}-text-font-weight`,
69
+ }];
70
+ }
71
+
72
+ export const contrastPairs: ContrastPair[] = variants.flatMap(variantContrastPairs);
60
73
  </script>
61
74
 
62
75
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildSiblings } from './scaffolding/siblings';
3
3
  import type { Token, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'button';
6
7
 
@@ -120,11 +121,41 @@
120
121
  default: { default: 'var(--color-transparent)' },
121
122
  },
122
123
  ];
124
+
125
+ // The small size keeps each variant's colors, and it is the smaller of the two text sizes.
126
+ const pairFont = { fontSize: '--button-small-text-font-size', fontWeight: '--button-small-text-font-weight' } as const;
127
+
128
+ function variantContrastPairs(v: Variant): ContrastPair[] {
129
+ const text = (s: StateName) => `${statePrefix(v, s)}-text`;
130
+ const surface = (s: StateName) => `${statePrefix(v, s)}-surface`;
131
+ const hoverStack = ['--button-hover-tint-enabled', surface('hover')];
132
+ const disabled: ContrastPair = { foreground: text('disabled'), backdrop: [surface('disabled')], role: 'text', ...pairFont, inactive: true };
133
+ if (v !== 'outline') {
134
+ return [
135
+ { foreground: text('default'), backdrop: [surface('default')], role: 'text', ...pairFont },
136
+ { foreground: text('hover'), backdrop: hoverStack, role: 'text', ...pairFont },
137
+ disabled,
138
+ ];
139
+ }
140
+ // `:active` repaints the surface without the tint. A pointer press keeps the
141
+ // hover text color, and a key press keeps the default one.
142
+ const active = ['--button-outline-active-surface'];
143
+ return [
144
+ { foreground: text('default'), backdrop: [surface('default')], role: 'text', ...pairFont, context: 'default' },
145
+ { foreground: text('default'), backdrop: active, role: 'text', ...pairFont, context: 'active' },
146
+ { foreground: text('hover'), backdrop: hoverStack, role: 'text', ...pairFont, context: 'hover' },
147
+ { foreground: text('hover'), backdrop: active, role: 'text', ...pairFont, context: 'active' },
148
+ disabled,
149
+ ];
150
+ }
151
+
152
+ export const contrastPairs: ContrastPair[] = variants.flatMap(variantContrastPairs);
123
153
  </script>
124
154
 
125
155
  <script lang="ts">
126
156
  import Button from '../../system/components/Button.svelte';
127
157
  import Toggle from '../ui/Toggle.svelte';
158
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
128
159
  import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
129
160
  import VariantGroup from './scaffolding/VariantGroup.svelte';
130
161
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
@@ -216,7 +247,7 @@
216
247
  {#snippet extraPropertyRows(stateName)}
217
248
  {#if stateName === 'hover'}
218
249
  <div class="property-row" data-token-variables="--button-hover-tint-enabled">
219
- <span class="property-label">tint layer</span>
250
+ <span class="property-label">tint layer<A11yPropertyMark variables={['--button-hover-tint-enabled']} /></span>
220
251
  <Toggle checked={tintOn} onchange={setTintOn} />
221
252
  </div>
222
253
  {#if tintOn}
@@ -226,7 +257,7 @@
226
257
  </div>
227
258
  {/if}
228
259
  <div class="property-row" data-token-variables="--button-shimmer">
229
- <span class="property-label">hover shimmer</span>
260
+ <span class="property-label">hover shimmer<A11yPropertyMark variables={['--button-shimmer']} /></span>
230
261
  <Toggle checked={shimmerEnabled} onchange={handleShimmerChange} />
231
262
  </div>
232
263
  {/if}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
  import { calloutVariants } from '../../system/components/Callout.svelte';
5
6
 
6
7
  export const component = 'callout';
@@ -68,6 +69,18 @@
68
69
  );
69
70
 
70
71
  const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
72
+
73
+ function variantContrastPairs(v: Variant): ContrastPair[] {
74
+ return variantTypeGroups(v).map((g) => ({
75
+ foreground: g.colorVariable,
76
+ backdrop: [`--callout-${v}-surface`],
77
+ role: 'text',
78
+ fontSize: g.sizeVariable,
79
+ fontWeight: g.weightVariable,
80
+ }));
81
+ }
82
+
83
+ export const contrastPairs: ContrastPair[] = variants.flatMap(variantContrastPairs);
71
84
  </script>
72
85
 
73
86
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'card';
6
7
 
@@ -104,12 +105,20 @@
104
105
  default: { default: HOVER_OFF.shadow },
105
106
  },
106
107
  ];
108
+
109
+ // size="small" sets the title in --font-size-md and the body in
110
+ // --font-size-sm, the smaller of each one's two sizes.
111
+ export const contrastPairs: ContrastPair[] = [
112
+ { foreground: '--card-default-title', backdrop: ['--card-default-header-surface', '--card-default-surface'], role: 'text', fontSize: '--font-size-md', fontWeight: '--card-default-title-font-weight' },
113
+ { foreground: '--card-default-body', backdrop: ['--card-default-surface'], role: 'text', fontSize: '--font-size-sm', fontWeight: '--card-default-body-font-weight' },
114
+ ];
107
115
  </script>
108
116
 
109
117
  <script lang="ts">
110
118
  import Card from '../../system/components/Card.svelte';
111
119
  import VariantGroup from './scaffolding/VariantGroup.svelte';
112
120
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
121
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
113
122
  import { editorState, setComponentAlias } from '../core/store/editorStore';
114
123
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
115
124
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
@@ -152,6 +161,7 @@
152
161
  <input type="checkbox" checked={hoverEnabled} onchange={(e) => setHoverEnabled(e.currentTarget.checked)} />
153
162
  <span>Use hover</span>
154
163
  </label>
164
+ <A11yPropertyMark variables={['--card-hover-border-enabled', '--card-hover-shadow-enabled']} />
155
165
  <p class="hover-help">
156
166
  Checked: every card lifts on hover. Unchecked: hover is off by default and a page turns it on
157
167
  per card with the <code>hover</code> prop.
@@ -163,10 +173,10 @@
163
173
  {@const previewClass = activeState === 'hover' ? 'force-hover' : ''}
164
174
  <div class="card-demo">
165
175
  <Card title="Card title" icon="fas fa-star" class={previewClass}>
166
- <div class="content-placeholder">Content Placeholder</div>
176
+ <div class="content-placeholder" data-preview-demo>Content Placeholder</div>
167
177
  </Card>
168
178
  <Card variant="bare" class={previewClass}>
169
- <div class="content-placeholder">Bare variant</div>
179
+ <div class="content-placeholder" data-preview-demo>Bare variant</div>
170
180
  </Card>
171
181
  </div>
172
182
  {/snippet}
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import type { Token } from './scaffolding/types';
3
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
3
4
 
4
5
  export const component = 'codesnippet';
5
6
 
@@ -33,6 +34,15 @@
33
34
  };
34
35
 
35
36
  export const allTokens: Token[] = Object.values(states).flat();
37
+
38
+ // The copy button and the scrollbar track paint no surface of their own.
39
+ const surface = ['--codesnippet-surface'];
40
+ export const contrastPairs: ContrastPair[] = [
41
+ { foreground: '--codesnippet-code-text', backdrop: surface, role: 'text', fontSize: '--codesnippet-code-font-size', fontWeight: '--codesnippet-code-font-weight' },
42
+ { foreground: '--codesnippet-icon', backdrop: surface, role: 'non-text' },
43
+ { foreground: '--codesnippet-hover-icon', backdrop: surface, role: 'non-text' },
44
+ { foreground: '--codesnippet-scrollbar-thumb', backdrop: surface, role: 'non-text' },
45
+ ];
36
46
  </script>
37
47
 
38
48
  <script lang="ts">
@@ -52,7 +62,7 @@
52
62
  <VariantGroup name="codesnippet" title="Code Snippet" {states} {component}>
53
63
  {#snippet children({ activeState })}
54
64
  <div class="codesnippet-preview">
55
- <label class="preview-text">
65
+ <label class="preview-text" data-preview-demo>
56
66
  <span>Preview text</span>
57
67
  <textarea
58
68
  bind:value={previewCode}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'collapsiblesection';
6
7
 
@@ -117,6 +118,22 @@
117
118
  ]);
118
119
 
119
120
  const variantOptions = VARIANTS.map((v) => ({ value: v, label: VARIANT_LABELS[v] }));
121
+
122
+ function headerContrastPairs(v: Variant, s: HeaderState): ContrastPair[] {
123
+ const backdrop = [`--collapsiblesection-${v}-${s}-surface`];
124
+ return [
125
+ ...variantTypeGroups(v)[`Header / ${HEADER_STATE_LABELS[s]}`].map((g): ContrastPair => ({
126
+ foreground: g.colorVariable,
127
+ backdrop,
128
+ role: 'text',
129
+ fontSize: g.sizeVariable,
130
+ fontWeight: g.weightVariable,
131
+ })),
132
+ { foreground: `--collapsiblesection-${v}-${s}-icon`, backdrop, role: 'non-text' },
133
+ ];
134
+ }
135
+
136
+ export const contrastPairs: ContrastPair[] = VARIANTS.flatMap((v) => HEADER_STATES.flatMap((s) => headerContrastPairs(v, s)));
120
137
  </script>
121
138
 
122
139
  <script lang="ts">
@@ -155,7 +172,7 @@
155
172
  open={isBody}
156
173
  class={forceClass}
157
174
  >
158
- <p style="margin: 0; color: var(--text-secondary);">
175
+ <p style="margin: 0; color: var(--text-secondary);" data-preview-demo>
159
176
  {#if isContainerPart}
160
177
  (Container) — outer chrome only; switch to Body to see the content area.
161
178
  {:else}
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import type { Token } from './scaffolding/types';
3
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
3
4
  import { badgeVariants } from '../../system/components/Badge.svelte';
4
5
 
5
6
  export const component = 'cornerbadge';
@@ -40,6 +41,18 @@
40
41
  ];
41
42
 
42
43
  const variantOptions = badgeVariants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
44
+
45
+ function variantContrastPairs(v: Variant): ContrastPair[] {
46
+ return [{
47
+ foreground: `--cornerbadge-${v}-text`,
48
+ backdrop: [`--cornerbadge-${v}-surface`],
49
+ role: 'text',
50
+ fontSize: '--cornerbadge-text-font-size',
51
+ fontWeight: '--cornerbadge-text-font-weight',
52
+ }];
53
+ }
54
+
55
+ export const contrastPairs: ContrastPair[] = badgeVariants.flatMap(variantContrastPairs);
43
56
  </script>
44
57
 
45
58
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'dialog';
6
7
 
@@ -85,6 +86,20 @@
85
86
  function variantLabel(v: ButtonVariant): string {
86
87
  return v.charAt(0).toUpperCase() + v.slice(1);
87
88
  }
89
+
90
+ // The header and body sit on the dialog box, and the box sits on the scrim.
91
+ const boxStack = ['--dialog-surface', '--dialog-scrim-surface'];
92
+ const headerStack = ['--dialog-header-surface', ...boxStack];
93
+ const textPair = (part: string, backdrop: string[]): ContrastPair => {
94
+ const g = frameTypeGroups[part][0];
95
+ return { foreground: g.colorVariable, backdrop, role: 'text', fontSize: g.sizeVariable, fontWeight: g.weightVariable };
96
+ };
97
+
98
+ export const contrastPairs: ContrastPair[] = [
99
+ textPair('header', headerStack),
100
+ { foreground: '--dialog-close-icon', backdrop: headerStack, role: 'non-text' },
101
+ textPair('body', boxStack),
102
+ ];
88
103
  </script>
89
104
 
90
105
  <script lang="ts">