@motion-proto/live-tokens 0.78.0 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +49 -38
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +23 -28
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +61 -0
  4. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +2 -2
  5. package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -15
  6. package/.claude/skills/live-tokens-create-page/SKILL.md +6 -4
  7. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
  8. package/.claude/skills/live-tokens-pick-component/SKILL.md +3 -3
  9. package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +1 -1
  10. package/.claude/skills/live-tokens-set-geometry/SKILL.md +1 -1
  11. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +1 -1
  12. package/CHANGELOG.md +271 -0
  13. package/README.md +10 -16
  14. package/bin/check-component.mjs +154 -551
  15. package/bin/check-page.mjs +57 -541
  16. package/bin/cli.mjs +93 -16
  17. package/bin/contractRunner.mjs +37 -91
  18. package/bin/lib/buildChecks.mjs +32 -0
  19. package/bin/lib/catalogue.mjs +161 -34
  20. package/bin/lib/componentSource.mjs +152 -0
  21. package/bin/lib/cssValues.mjs +9 -0
  22. package/bin/lib/dataDir.mjs +126 -0
  23. package/bin/lib/findings.mjs +103 -12
  24. package/bin/lib/fixers.mjs +64 -0
  25. package/bin/lib/geometry.mjs +92 -0
  26. package/bin/lib/pageSource.mjs +230 -0
  27. package/bin/lib/report.mjs +57 -59
  28. package/bin/lib/tokenVocabulary.mjs +104 -34
  29. package/bin/migrate-build-script.mjs +66 -0
  30. package/bin/migrate.mjs +5 -0
  31. package/bin/rules/componentStructure.mjs +344 -0
  32. package/bin/rules/componentUse.mjs +313 -0
  33. package/bin/rules/importsAndRoutes.mjs +136 -0
  34. package/bin/rules/testRuns.mjs +122 -0
  35. package/bin/rules/tokens.mjs +426 -0
  36. package/bin/setup-claude.mjs +1 -2
  37. package/dist-plugin/{chunk-PDNL4NC5.js → chunk-D4WRIKEZ.js} +7 -2
  38. package/dist-plugin/{chunk-SWXRVZKT.js → chunk-REBHE3ZM.js} +414 -1
  39. package/dist-plugin/index.cjs +470 -19
  40. package/dist-plugin/index.d.cts +1 -0
  41. package/dist-plugin/index.d.ts +1 -0
  42. package/dist-plugin/index.js +87 -55
  43. package/dist-plugin/migrateData/index.cjs +422 -4
  44. package/dist-plugin/migrateData/index.js +2 -2
  45. package/dist-plugin/setColors/index.cjs +414 -1
  46. package/dist-plugin/setColors/index.d.cts +1 -1
  47. package/dist-plugin/setColors/index.d.ts +1 -1
  48. package/dist-plugin/setColors/index.js +1 -1
  49. package/dist-plugin/setGeometry/index.cjs +423 -13
  50. package/dist-plugin/setGeometry/index.d.cts +3 -3
  51. package/dist-plugin/setGeometry/index.d.ts +3 -3
  52. package/dist-plugin/setGeometry/index.js +10 -13
  53. package/dist-plugin/setType/index.d.cts +1 -1
  54. package/dist-plugin/setType/index.d.ts +1 -1
  55. package/dist-plugin/{themeTypes-BxRtuN5V.d.cts → themeTypes-B8_Idrp4.d.cts} +2 -2
  56. package/dist-plugin/{themeTypes-BxRtuN5V.d.ts → themeTypes-B8_Idrp4.d.ts} +2 -2
  57. package/package.json +2 -2
  58. package/src/editor/component-editor/CalloutEditor.svelte +2 -2
  59. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +14 -15
  60. package/src/editor/component-editor/CornerBadgeEditor.svelte +14 -14
  61. package/src/editor/component-editor/DialogEditor.svelte +5 -5
  62. package/src/editor/component-editor/InlineEditActionsEditor.svelte +2 -2
  63. package/src/editor/component-editor/RadioButtonEditor.svelte +21 -21
  64. package/src/editor/component-editor/SectionDividerEditor.svelte +7 -7
  65. package/src/editor/component-editor/SegmentedControlEditor.svelte +5 -5
  66. package/src/editor/component-editor/SideNavigationEditor.svelte +25 -25
  67. package/src/editor/component-editor/TabBarEditor.svelte +6 -6
  68. package/src/editor/component-editor/TableEditor.svelte +6 -6
  69. package/src/editor/component-editor/ToggleEditor.svelte +2 -2
  70. package/src/editor/component-editor/index.ts +3 -0
  71. package/src/editor/component-editor/registry.ts +57 -1
  72. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +14 -14
  73. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +3 -0
  74. package/src/editor/component-editor/scaffolding/types.ts +15 -0
  75. package/src/editor/core/components/adjustAliases.ts +4 -4
  76. package/src/editor/core/components/aliasKinds.ts +20 -19
  77. package/src/editor/core/sketch/sketchLayer.ts +3 -3
  78. package/src/editor/core/themes/migrateComponentConfig.ts +14 -6
  79. package/src/editor/core/themes/migrations/2026-09-13-badge-brand.ts +42 -0
  80. package/src/editor/core/themes/migrations/2026-09-13-collapsiblesection-open.ts +33 -0
  81. package/src/editor/core/themes/migrations/2026-09-13-cornerbadge-prefix.ts +70 -0
  82. package/src/editor/core/themes/migrations/2026-09-13-hairline.ts +92 -0
  83. package/src/editor/core/themes/migrations/2026-09-13-indicator.ts +54 -0
  84. package/src/editor/core/themes/migrations/2026-09-13-sectiondivider-surface.ts +36 -0
  85. package/src/editor/core/themes/migrations/2026-09-13-selected-state.ts +124 -0
  86. package/src/editor/core/themes/migrations/2026-09-13-toggle-label.ts +31 -0
  87. package/src/editor/core/themes/migrations/index.ts +16 -0
  88. package/src/editor/core/themes/themeService.ts +3 -3
  89. package/src/editor/core/themes/themeTypes.ts +13 -15
  90. package/src/editor/docs/Docs.svelte +1 -1
  91. package/src/editor/docs/content/light-and-dark.md +3 -3
  92. package/src/editor/docs/content.generated.ts +1 -1
  93. package/src/editor/index.ts +1 -0
  94. package/src/editor/overlay/LiveEditorOverlay.svelte +15 -0
  95. package/src/editor/skill-atlas/SkillAtlas.svelte +4 -4
  96. package/src/editor/skill-atlas/TreeNodeCard.svelte +4 -4
  97. package/src/editor/skill-atlas/skillSources.generated.ts +11 -14
  98. package/src/editor/skill-atlas/skillTrees.ts +1 -3
  99. package/src/editor/skill-atlas/trees/check-compliance.ts +37 -149
  100. package/src/editor/skill-atlas/trees/create-component.ts +62 -64
  101. package/src/editor/skill-atlas/trees/create-page.ts +38 -17
  102. package/src/editor/skill-atlas/trees/pick-component.ts +3 -3
  103. package/src/editor/skill-atlas/trees/set-geometry.ts +1 -1
  104. package/src/editor/ui/UIPaletteSelector.svelte +11 -15
  105. package/src/editor/ui/variantScales.ts +8 -8
  106. package/src/live-tokens/data/themes/autumn.json +188 -188
  107. package/src/live-tokens/data/themes/halloween.json +188 -188
  108. package/src/live-tokens/data/themes/midnight-study.json +245 -245
  109. package/src/live-tokens/data/themes/ocean.json +188 -188
  110. package/src/live-tokens/data/themes/royal-velvet.json +188 -188
  111. package/src/live-tokens/data/themes/sketchy.json +188 -188
  112. package/src/live-tokens/data/themes/spring-meadow.json +188 -188
  113. package/src/live-tokens/data/themes/sunset.json +188 -188
  114. package/src/system/components/Badge.svelte +27 -23
  115. package/src/system/components/Button.svelte +13 -7
  116. package/src/system/components/Callout.svelte +16 -11
  117. package/src/system/components/Card.svelte +22 -15
  118. package/src/system/components/CodeSnippet.svelte +10 -6
  119. package/src/system/components/CollapsibleSection.svelte +81 -76
  120. package/src/system/components/CornerBadge.svelte +78 -72
  121. package/src/system/components/Dialog.svelte +21 -18
  122. package/src/system/components/IconButton.svelte +13 -9
  123. package/src/system/components/Image.svelte +14 -8
  124. package/src/system/components/ImageLightbox.svelte +10 -6
  125. package/src/system/components/InlineEditActions.svelte +17 -14
  126. package/src/system/components/Input.svelte +13 -7
  127. package/src/system/components/MenuSelect.svelte +13 -7
  128. package/src/system/components/Notification.svelte +17 -11
  129. package/src/system/components/Panel.svelte +13 -6
  130. package/src/system/components/ProgressBar.svelte +10 -5
  131. package/src/system/components/RadioButton.svelte +33 -30
  132. package/src/system/components/SectionDivider.svelte +28 -21
  133. package/src/system/components/SegmentedControl.svelte +27 -23
  134. package/src/system/components/SideNavigation.svelte +175 -171
  135. package/src/system/components/Slider.svelte +11 -7
  136. package/src/system/components/TabBar.svelte +53 -49
  137. package/src/system/components/Table.svelte +20 -15
  138. package/src/system/components/Toggle.svelte +14 -10
  139. package/src/system/components/Tooltip.svelte +10 -6
  140. package/src/system/styles/CONVENTIONS.md +3 -4
  141. package/src/testing-js/chunk-3UKGXCDL.js +48 -0
  142. package/src/testing-js/chunk-3UKGXCDL.js.map +1 -0
  143. package/src/testing-js/{chunk-FAFOAWYL.js → chunk-Q3YIAAG3.js} +17 -3
  144. package/src/testing-js/chunk-Q3YIAAG3.js.map +1 -0
  145. package/src/testing-js/{chunk-GNIUPIU2.js → chunk-U7OJE5DU.js} +18 -18
  146. package/src/testing-js/chunk-U7OJE5DU.js.map +1 -0
  147. package/src/testing-js/{chunk-4JQX6WWL.js → chunk-ZMZQZ33J.js} +474 -166
  148. package/src/testing-js/chunk-ZMZQZ33J.js.map +1 -0
  149. package/src/testing-js/component-behavior.contract.js +155 -0
  150. package/src/testing-js/component-behavior.contract.js.map +1 -0
  151. package/src/testing-js/component-editor.contract.js +6 -4
  152. package/src/testing-js/component-editor.contract.js.map +1 -1
  153. package/src/testing-js/component-render.contract.js +14 -10
  154. package/src/testing-js/component-render.contract.js.map +1 -1
  155. package/src/testing-js/index.d.ts +44 -4
  156. package/src/testing-js/index.js +13 -7
  157. package/src/testing-js/index.js.map +1 -1
  158. package/src/testing-js/page-compliance.contract.js +48 -10
  159. package/src/testing-js/page-compliance.contract.js.map +1 -1
  160. package/src/testing-js/registry.contract.js +5 -3
  161. package/src/testing-js/registry.contract.js.map +1 -1
  162. package/src/testing-js/{vitest-C-wNWcoA.d.ts → vitest-BMLIbDs2.d.ts} +8 -2
  163. package/src/testing-js/vitest.d.ts +1 -1
  164. package/src/testing-js/vitest.js +2 -1
  165. package/template/package.json +1 -2
  166. package/template/vite.config.ts +3 -2
  167. package/.claude/skills/live-tokens-create-component/SKILL copy.md +0 -196
  168. package/.claude/skills/live-tokens-fix-findings/SKILL.md +0 -105
  169. package/src/editor/skill-atlas/trees/fix-findings.ts +0 -504
  170. package/src/testing-js/chunk-4JQX6WWL.js.map +0 -1
  171. package/src/testing-js/chunk-FAFOAWYL.js.map +0 -1
  172. package/src/testing-js/chunk-GNIUPIU2.js.map +0 -1
@@ -1,11 +1,15 @@
1
- <!--
2
- Slider.svelte. A number the reader picks by position on a track.
3
- Use for: a volume, a price band, or a percentage, where the position carries
4
- the meaning.
5
- Not for: an exact number the reader would rather type (Input); an on/off
6
- setting (Toggle).
7
- -->
8
1
  <script module lang="ts">
2
+ import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
+
4
+ export const catalogue = {
5
+ description: 'A number the reader picks by position on a track.',
6
+ useFor: 'a volume, a price range, or a percentage, where the position carries the meaning.',
7
+ notFor: 'an exact number the reader would rather type (Input); an on/off setting (Toggle).',
8
+ props: {
9
+ variant: '`single` picks one number; `range` picks a low and a high.',
10
+ },
11
+ } satisfies CatalogueEntry;
12
+
9
13
  export const sliderVariants = ['single', 'range'] as const;
10
14
  export type SliderVariant = typeof sliderVariants[number];
11
15
  </script>
@@ -1,9 +1,13 @@
1
- <!--
2
- TabBar.svelte. Tabs that swap the content below them.
3
- Use for: two to seven panels the reader moves between inside one page.
4
- Not for: one switch among others in a row (SegmentedControl); navigation
5
- that changes the URL (SideNavigation).
6
- -->
1
+ <script module lang="ts">
2
+ import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
+
4
+ export const catalogue = {
5
+ description: 'Tabs that swap the content below them.',
6
+ useFor: 'two to seven views the reader moves between inside one page.',
7
+ notFor: 'one switch among others in a row (SegmentedControl); navigation that changes the URL (SideNavigation).',
8
+ } satisfies CatalogueEntry;
9
+ </script>
10
+
7
11
  <script lang="ts">
8
12
  import { createEventDispatcher } from 'svelte';
9
13
 
@@ -19,20 +23,20 @@
19
23
  global default; `true`/`false` force this instance on/off. */
20
24
  hoverTint?: boolean | undefined;
21
25
  tabs?: Tab[];
22
- selectedTab?: string;
26
+ value?: string;
23
27
  iconOnly?: boolean;
24
28
  class?: string;
25
- /** Tab-change callback. Preferred over `on:tabChange` from 0.5.0 onward. */
26
- ontabChange?: (id: string) => void;
29
+ /** Fires with the id of the tab the reader picked. */
30
+ onchange?: (id: string) => void;
27
31
  }
28
32
 
29
33
  let {
30
34
  hoverTint = undefined,
31
35
  tabs = [],
32
- selectedTab = '',
36
+ value = '',
33
37
  iconOnly = false,
34
38
  class: className = '',
35
- ontabChange
39
+ onchange
36
40
  }: Props = $props();
37
41
 
38
42
  // Per-instance override of the global hover-tint intrinsic; undefined leaves :root in charge.
@@ -47,7 +51,7 @@
47
51
 
48
52
  function selectTab(tab: Tab) {
49
53
  if (!tab.disabled) {
50
- ontabChange?.(tab.id);
54
+ onchange?.(tab.id);
51
55
  dispatch('tabChange', tab.id);
52
56
  }
53
57
  }
@@ -60,7 +64,7 @@
60
64
  {#each tabs as tab}
61
65
  <button
62
66
  class="tab"
63
- class:active={selectedTab === tab.id}
67
+ class:selected={value === tab.id}
64
68
  class:icon-only={iconOnly}
65
69
  disabled={tab.disabled}
66
70
  onclick={() => selectTab(tab)}
@@ -85,8 +89,8 @@
85
89
  --tabbar-hover-tint-enabled: var(--color-transparent);
86
90
 
87
91
  /* Bar */
88
- --tabbar-bar-divider: var(--border-neutral-subtle);
89
- --tabbar-bar-divider-thickness: var(--border-width-1);
92
+ --tabbar-bar-hairline-color: var(--border-neutral-subtle);
93
+ --tabbar-bar-hairline-width: var(--border-width-1);
90
94
  --tabbar-bar-top-margin: var(--space-0);
91
95
  --tabbar-bar-bottom-padding: var(--space-0);
92
96
  --tabbar-bar-bottom-margin: var(--space-0);
@@ -124,21 +128,21 @@
124
128
  --tabbar-hover-tab-bottom-radius: var(--radius-none);
125
129
  --tabbar-hover-indicator-width: var(--border-width-2);
126
130
 
127
- /* Active tab */
128
- --tabbar-active-text: var(--text-primary);
129
- --tabbar-active-border: var(--color-brand-500);
130
- --tabbar-active-surface: var(--tint-low);
131
- --tabbar-active-text-font-family: var(--font-sans);
132
- --tabbar-active-text-font-size: var(--font-size-md);
133
- --tabbar-active-text-font-weight: var(--font-weight-light);
134
- --tabbar-active-text-line-height: var(--line-height-normal);
135
- --tabbar-active-icon-size: var(--icon-size-md);
136
- --tabbar-active-padding: var(--space-8);
137
- --tabbar-active-tab-border-color: var(--color-transparent);
138
- --tabbar-active-tab-border-width: var(--border-width-0);
139
- --tabbar-active-tab-top-radius: var(--radius-none);
140
- --tabbar-active-tab-bottom-radius: var(--radius-none);
141
- --tabbar-active-indicator-width: var(--border-width-2);
131
+ /* Selected tab */
132
+ --tabbar-selected-text: var(--text-primary);
133
+ --tabbar-selected-border: var(--color-brand-500);
134
+ --tabbar-selected-surface: var(--tint-low);
135
+ --tabbar-selected-text-font-family: var(--font-sans);
136
+ --tabbar-selected-text-font-size: var(--font-size-md);
137
+ --tabbar-selected-text-font-weight: var(--font-weight-light);
138
+ --tabbar-selected-text-line-height: var(--line-height-normal);
139
+ --tabbar-selected-icon-size: var(--icon-size-md);
140
+ --tabbar-selected-padding: var(--space-8);
141
+ --tabbar-selected-tab-border-color: var(--color-transparent);
142
+ --tabbar-selected-tab-border-width: var(--border-width-0);
143
+ --tabbar-selected-tab-top-radius: var(--radius-none);
144
+ --tabbar-selected-tab-bottom-radius: var(--radius-none);
145
+ --tabbar-selected-indicator-width: var(--border-width-2);
142
146
 
143
147
  /* Disabled tab */
144
148
  --tabbar-disabled-text: var(--text-disabled);
@@ -160,7 +164,7 @@
160
164
  .tab-bar {
161
165
  display: flex;
162
166
  gap: var(--tabbar-tab-gap);
163
- border-bottom: var(--tabbar-bar-divider-thickness) solid var(--tabbar-bar-divider);
167
+ border-bottom: var(--tabbar-bar-hairline-width) solid var(--tabbar-bar-hairline-color);
164
168
  margin-top: var(--tabbar-bar-top-margin);
165
169
  padding-bottom: var(--tabbar-bar-bottom-padding);
166
170
  margin-bottom: var(--tabbar-bar-bottom-margin);
@@ -168,7 +172,7 @@
168
172
 
169
173
  /* Per-state tokens are bound to `--_*` custom properties below. The actual
170
174
  layout-affecting declarations (padding, border, border-radius, font-*)
171
- are written exactly once on `.tab`, so a hover/active state change can
175
+ are written exactly once on `.tab`, so a hover or selected state change can
172
176
  only repaint, not relayout. When two states resolve to the same value,
173
177
  the computed property is identical and no reshape is triggered. The
174
178
  `transition: all` of the old rule has also been narrowed to paint-only
@@ -218,8 +222,8 @@
218
222
  font-size: var(--_icon-size);
219
223
  }
220
224
 
221
- .tab:hover:not(:disabled):not(.active),
222
- .tab-bar.force-hover .tab:not(:disabled):not(.active) {
225
+ .tab:hover:not(:disabled):not(.selected),
226
+ .tab-bar.force-hover .tab:not(:disabled):not(.selected) {
223
227
  background-image: linear-gradient(var(--tabbar-hover-tint-enabled), var(--tabbar-hover-tint-enabled));
224
228
  --_text-color: var(--tabbar-hover-text);
225
229
  --_text-family: var(--tabbar-hover-text-font-family);
@@ -237,21 +241,21 @@
237
241
  --_bottom-radius: var(--tabbar-hover-tab-bottom-radius);
238
242
  }
239
243
 
240
- .tab.active {
241
- --_text-color: var(--tabbar-active-text);
242
- --_text-family: var(--tabbar-active-text-font-family);
243
- --_text-size: var(--tabbar-active-text-font-size);
244
- --_text-weight: var(--tabbar-active-text-font-weight);
245
- --_text-line-height: var(--tabbar-active-text-line-height);
246
- --_icon-size: var(--tabbar-active-icon-size);
247
- --_surface: var(--tabbar-active-surface);
248
- --_indicator-color: var(--tabbar-active-border);
249
- --_indicator-width: var(--tabbar-active-indicator-width);
250
- --_padding: var(--tabbar-active-padding);
251
- --_border-color: var(--tabbar-active-tab-border-color);
252
- --_border-width: var(--tabbar-active-tab-border-width);
253
- --_top-radius: var(--tabbar-active-tab-top-radius);
254
- --_bottom-radius: var(--tabbar-active-tab-bottom-radius);
244
+ .tab.selected {
245
+ --_text-color: var(--tabbar-selected-text);
246
+ --_text-family: var(--tabbar-selected-text-font-family);
247
+ --_text-size: var(--tabbar-selected-text-font-size);
248
+ --_text-weight: var(--tabbar-selected-text-font-weight);
249
+ --_text-line-height: var(--tabbar-selected-text-line-height);
250
+ --_icon-size: var(--tabbar-selected-icon-size);
251
+ --_surface: var(--tabbar-selected-surface);
252
+ --_indicator-color: var(--tabbar-selected-border);
253
+ --_indicator-width: var(--tabbar-selected-indicator-width);
254
+ --_padding: var(--tabbar-selected-padding);
255
+ --_border-color: var(--tabbar-selected-tab-border-color);
256
+ --_border-width: var(--tabbar-selected-tab-border-width);
257
+ --_top-radius: var(--tabbar-selected-tab-top-radius);
258
+ --_bottom-radius: var(--tabbar-selected-tab-bottom-radius);
255
259
  }
256
260
 
257
261
  .tab:disabled {
@@ -1,8 +1,13 @@
1
- <!--
2
- Table.svelte. A themed frame around the rows and cells the page writes.
3
- Use for: records the reader scans and compares.
4
- Not for: a set of things the reader acts on (Card).
5
- -->
1
+ <script module lang="ts">
2
+ import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
+
4
+ export const catalogue = {
5
+ description: 'A themed frame around the rows and cells the page writes.',
6
+ useFor: 'records the reader scans and compares.',
7
+ notFor: 'a set of things the reader acts on (Card).',
8
+ } satisfies CatalogueEntry;
9
+ </script>
10
+
6
11
  <script lang="ts">
7
12
  interface Props {
8
13
  children?: import('svelte').Snippet;
@@ -34,8 +39,8 @@
34
39
  --table-default-header-font-weight: var(--font-weight-semibold);
35
40
  --table-default-header-line-height: var(--line-height-none);
36
41
  --table-default-header-padding: var(--space-12);
37
- --table-default-header-divider: var(--border-neutral);
38
- --table-default-header-divider-width: var(--border-width-1);
42
+ --table-default-header-hairline-color: var(--border-neutral);
43
+ --table-default-header-hairline-width: var(--border-width-1);
39
44
 
40
45
  /* Cell */
41
46
  --table-default-cell-text: var(--text-secondary);
@@ -47,13 +52,13 @@
47
52
 
48
53
  /* Row */
49
54
  --table-default-row-surface: var(--color-transparent);
50
- --table-default-row-divider: var(--border-neutral-subtle);
51
- --table-default-row-divider-width: var(--border-width-1);
55
+ --table-default-row-hairline-color: var(--border-neutral-subtle);
56
+ --table-default-row-hairline-width: var(--border-width-1);
52
57
  --table-default-row-stripe-surface: var(--color-transparent);
53
58
 
54
59
  /* Column */
55
- --table-default-column-divider: var(--border-neutral-faint);
56
- --table-default-column-divider-width: var(--border-width-1);
60
+ --table-default-column-hairline-color: var(--border-neutral-faint);
61
+ --table-default-column-hairline-width: var(--border-width-1);
57
62
  }
58
63
 
59
64
  .table-wrapper {
@@ -82,8 +87,8 @@
82
87
  line-height: var(--table-default-header-line-height);
83
88
  @include themed-padding(--table-default-header-padding);
84
89
  text-align: left;
85
- border-bottom: var(--table-default-header-divider-width) solid var(--table-default-header-divider);
86
- border-right: var(--table-default-column-divider-width) solid var(--table-default-column-divider);
90
+ border-bottom: var(--table-default-header-hairline-width) solid var(--table-default-header-hairline-color);
91
+ border-right: var(--table-default-column-hairline-width) solid var(--table-default-column-hairline-color);
87
92
  white-space: nowrap;
88
93
  }
89
94
 
@@ -99,8 +104,8 @@
99
104
  var(--table-default-cell-padding-right, calc(var(--table-default-cell-padding) * 1.5))
100
105
  var(--table-default-cell-padding-bottom, var(--table-default-cell-padding))
101
106
  var(--table-default-cell-padding-left, calc(var(--table-default-cell-padding) * 1.5));
102
- border-bottom: var(--table-default-row-divider-width) solid var(--table-default-row-divider);
103
- border-right: var(--table-default-column-divider-width) solid var(--table-default-column-divider);
107
+ border-bottom: var(--table-default-row-hairline-width) solid var(--table-default-row-hairline-color);
108
+ border-right: var(--table-default-column-hairline-width) solid var(--table-default-column-hairline-color);
104
109
  }
105
110
 
106
111
  .table-wrapper :global(th:last-child),
@@ -1,9 +1,13 @@
1
- <!--
2
- Toggle.svelte. An on/off switch that names the setting it controls.
3
- Use for: a setting that takes effect the moment it flips.
4
- Not for: a choice between two named alternatives (SegmentedControl); a
5
- yes/no answer inside a form (RadioButton).
6
- -->
1
+ <script module lang="ts">
2
+ import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
+
4
+ export const catalogue = {
5
+ description: 'An on/off switch that names the setting it controls.',
6
+ useFor: 'a setting that takes effect the moment it flips.',
7
+ notFor: 'a choice between two named alternatives (SegmentedControl); a yes/no answer inside a form (RadioButton).',
8
+ } satisfies CatalogueEntry;
9
+ </script>
10
+
7
11
  <script lang="ts">
8
12
  interface Props {
9
13
  checked?: boolean;
@@ -59,7 +63,7 @@
59
63
  --toggle-thumb-surface: var(--surface-neutral-highest);
60
64
  --toggle-thumb-border: var(--border-neutral-strong);
61
65
  --toggle-thumb-size: var(--font-size-md);
62
- --toggle-label-text: var(--text-primary);
66
+ --toggle-label: var(--text-primary);
63
67
  --toggle-label-font-family: var(--font-sans);
64
68
  --toggle-label-font-size: var(--font-size-sm);
65
69
  --toggle-label-font-weight: var(--font-weight-normal);
@@ -82,7 +86,7 @@
82
86
  /* Disabled (terminal — applies regardless of on/off). */
83
87
  --toggle-disabled-track-surface: var(--surface-neutral-lower);
84
88
  --toggle-disabled-thumb-surface: var(--surface-neutral);
85
- --toggle-disabled-label-text: var(--text-disabled);
89
+ --toggle-disabled-label: var(--text-disabled);
86
90
  }
87
91
 
88
92
  .toggle {
@@ -126,7 +130,7 @@
126
130
  }
127
131
 
128
132
  .label {
129
- color: var(--toggle-label-text);
133
+ color: var(--toggle-label);
130
134
  font-family: var(--toggle-label-font-family);
131
135
  font-size: var(--toggle-label-font-size);
132
136
  font-weight: var(--toggle-label-font-weight);
@@ -174,6 +178,6 @@
174
178
  border-color: var(--toggle-disabled-thumb-surface);
175
179
  }
176
180
  .toggle:disabled .label {
177
- color: var(--toggle-disabled-label-text);
181
+ color: var(--toggle-disabled-label);
178
182
  }
179
183
  </style>
@@ -1,9 +1,13 @@
1
- <!--
2
- Tooltip.svelte. A hint that appears on an element on hover or focus.
3
- Use for: a definition or a shortcut the reader can do without.
4
- Not for: content the reader must not miss (Callout); feedback after an
5
- action (Notification).
6
- -->
1
+ <script module lang="ts">
2
+ import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
+
4
+ export const catalogue = {
5
+ description: 'A short note that appears on an element on hover or focus.',
6
+ useFor: 'a definition or a shortcut the reader can do without.',
7
+ notFor: 'content the reader must not miss (Callout); feedback after an action (Notification).',
8
+ } satisfies CatalogueEntry;
9
+ </script>
10
+
7
11
  <script lang="ts">
8
12
  interface Props {
9
13
  text?: string;
@@ -87,7 +87,7 @@ Component tokens *reference* theme tokens. They're how a component names its own
87
87
  ```
88
88
 
89
89
  - **`componentId`.** The literal component ID. No abbreviations. The ID itself has no dashes (it's the file-system-safe form: `segmentedcontrol`, not `segmented-control`).
90
- - **`part`.** Which part of the component this slot belongs to: `bar`, `divider`, `option`, `selected`, `track`, `save`, `cancel`, etc.
90
+ - **`part`.** Which part of the component this slot belongs to: `bar`, `hairline`, `option`, `selected`, `track`, `save`, `cancel`, etc.
91
91
  - **`state`.** Optional. The interaction state if more than default: `hover`, `disabled`, `active`, `focus`. States come **before** the property, never after.
92
92
  - **`element`.** Optional. A sub-element within the part: `dot`, `icon`, `label`, `text`.
93
93
  - **`property`.** Always last. Either a **theme role** (`surface`, `border`, `text`, `icon`, `label`, `fill`) or a **CSS property name** (`radius`, `border-width`, `font-weight`, `font-family`, `font-size`).
@@ -113,11 +113,10 @@ Component tokens *reference* theme tokens. They're how a component names its own
113
113
  | `-font-family` | Font family reference |
114
114
  | `-font-weight` | Font weight reference |
115
115
  | `-font-size` | Font size reference |
116
- | `-thickness` | Alternative stroke dimension (used where `-width` would alias another token under name-based fallback grouping) |
117
116
  | `-height` | Explicit height when `-width`'s sibling would collide under name-based fallback grouping |
118
117
  | `-color` | Generic color when none of the role words fits (rare) |
119
118
 
120
- **Why `thickness` and `height` sometimes stand in for `width`:** when an editor declares no explicit `groupKey` for a token, sibling grouping falls back to matching the final `-<property>` segment. If two unrelated slots both end in `-width` and neither has a `groupKey`, they get auto-linked. Either declare a `groupKey` per token in the editor (preferred) or use an alternative property word. The divider in SegmentedControl uses `--segmentedcontrol-divider-thickness` for legacy parity with the fallback rule, but it now also has `groupKey: 'divider-thickness'` declared in the editor. The `groupKey` is the source of truth.
119
+ **Why `height` sometimes stands in for `width`:** when an editor declares no explicit `groupKey` for a token, sibling grouping falls back to matching the final `-<property>` segment. If two unrelated slots both end in `-width` and neither has a `groupKey`, they get auto-linked. Declare a `groupKey` per token in the editor. The `groupKey` is the source of truth.
121
120
 
122
121
  ### State order matters
123
122
 
@@ -163,7 +162,7 @@ buildTypeGroupColorTokens(typeGroups, { component, variants: ['default', 'hover'
163
162
  buildTypeGroupTokens(typeGroups, { component, variants })
164
163
  ```
165
164
 
166
- The helper strips the `--<component>-` prefix and the variant/state segments, keeping the rest: `--sidenavigation-section-default-text` → `section-text`, `--card-default-title-font-family` → `title-font-family`. Two parts ending in the same word (`section-text`, `item-text`) stay distinct; the same slot across variants (`--badge-primary-text`, `--badge-accent-text`) collapses to one key. This is correct by construction — it cannot phantom-link independent slots.
165
+ The helper strips the `--<component>-` prefix and the variant/state segments, keeping the rest: `--sidenavigation-section-default-text` → `section-text`, `--card-default-title-font-family` → `title-font-family`. Two parts ending in the same word (`section-text`, `item-text`) stay distinct; the same slot across variants (`--badge-brand-text`, `--badge-accent-text`) collapses to one key. This is correct by construction — it cannot phantom-link independent slots.
167
166
 
168
167
  Precedence, most specific first: a per-group `colorGroupKey` (the durable one-line fix; never recomputed) > a `groupKeyFor` callback > the structural derivation (`{ component, variants }`). There is **no name-based fallback**: call `buildTypeGroupColorTokens` with none of these and its color tokens are emitted solo (no `groupKey`), never inferred from the last dash. The font helpers (`buildTypeGroupTokens`, `buildTypeGroupFontTokens`) still default to the bare `font-family`/`font-size`/… keys, which is correct for a single slot but merges fonts across multiple slots — `check-component` warns when a font helper is called bare across more than one slot.
169
168
 
@@ -0,0 +1,48 @@
1
+ // src/testing/componentContract.ts
2
+ var ContractViolation = class extends Error {
3
+ constructor(rule, component, message) {
4
+ super(`[${rule}] ${component}: ${message}`);
5
+ this.rule = rule;
6
+ this.component = component;
7
+ this.name = "ContractViolation";
8
+ }
9
+ rule;
10
+ component;
11
+ };
12
+ function isInapplicable(value) {
13
+ return typeof value === "object" && value !== null && value.applicable === false;
14
+ }
15
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
16
+ "button",
17
+ "switch",
18
+ "checkbox",
19
+ "radio",
20
+ "slider",
21
+ "spinbutton",
22
+ "textbox",
23
+ "combobox",
24
+ "listbox",
25
+ "menuitem",
26
+ "tab",
27
+ "link",
28
+ "searchbox",
29
+ "option"
30
+ ]);
31
+ function requiresInteraction(role) {
32
+ return role !== null && INTERACTIVE_ROLES.has(role);
33
+ }
34
+ function partLocator(contract, key) {
35
+ const declared = contract.parts[key];
36
+ if (declared === void 0) {
37
+ throw new ContractViolation("contract-render", contract.id, `no part named "${key}" is declared`);
38
+ }
39
+ return typeof declared === "string" ? { selector: declared } : declared;
40
+ }
41
+
42
+ export {
43
+ ContractViolation,
44
+ isInapplicable,
45
+ requiresInteraction,
46
+ partLocator
47
+ };
48
+ //# sourceMappingURL=chunk-3UKGXCDL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../testing/componentContract.ts"],"sourcesContent":["export type ContractRule =\n | 'contract-listed'\n | 'contract-render'\n | 'contract-alias'\n | 'contract-persist'\n | 'contract-theme'\n | 'contract-states'\n | 'contract-interaction'\n | 'contract-behavior'\n | 'contract-sketch'\n | 'contract-missing';\n\n/**\n * A failed obligation, carrying the rule id the CLI reports it under. A defect\n * fixture names the rule it expects, and `check-component --tests` maps a\n * reporter error onto a finding.\n */\nexport class ContractViolation extends Error {\n constructor(\n readonly rule: ContractRule,\n readonly component: string,\n message: string,\n ) {\n super(`[${rule}] ${component}: ${message}`);\n this.name = 'ContractViolation';\n }\n}\n\nexport interface Inapplicable {\n applicable: false;\n reason: string;\n}\n\nexport function isInapplicable(value: unknown): value is Inapplicable {\n return typeof value === 'object' && value !== null && (value as Inapplicable).applicable === false;\n}\n\nexport interface PartLocator {\n /** Resolved inside the preview stage unless `portal` is set. */\n selector: string;\n /** Read computed style from this pseudo-element of the part, e.g.\n * `'::after'`. Chromium's `getComputedStyle` only tolerates the bare form\n * (`'after'`) for `::before`/`::after`; use the full `::` syntax so a\n * future pseudo-element (`::placeholder`, `::marker`, ...) works the same\n * way without a silent fallback to the host element's own style. */\n pseudo?: string;\n /** Rendered outside the preview stage. Resolved against the document. */\n portal?: boolean;\n}\n\nexport type PartDeclaration = string | PartLocator;\n\n/**\n * An editor control the preview is gated behind: a canvas-toolbar checkbox, an\n * optional-content switch. `check` sets a checkbox to a state and is safe to\n * repeat; `value` picks a select option; neither clicks once.\n */\nexport interface ControlStep {\n kind: 'control';\n /** Selector inside the visible variant group. */\n selector: string;\n check?: boolean;\n value?: string;\n}\n\nexport type SetupStep = InteractionAction | ControlStep;\n\n/** Which VariantGroup, which state tab, and what has to happen first. */\nexport interface View {\n /** Variant tab label. Omitted when the editor renders one variant. */\n variant?: string;\n /** State tab label. Omitted when the editor renders no state strip. */\n state?: string;\n /** Run after the tabs are selected. Portaled and toolbar-gated parts exist\n * only once these have run. */\n setup?: SetupStep[];\n}\n\n/** Part key -> CSS property -> the design token that must drive it. */\nexport type PaintMap = Record<string, Record<string, string>>;\n\nexport interface PropertyExpectation extends View {\n paints: PaintMap;\n}\n\nexport interface StateExpectation extends View {\n state: string;\n /** Class the root part carries while the editor previews this state. */\n forceClass?: string;\n /** Part key -> attributes it must carry. A null value requires the absence. */\n attributes?: Record<string, Record<string, string | null>>;\n /** What the state repaints, as computed-value equality against its token. */\n paints?: PaintMap;\n}\n\n/**\n * `token` and `opacity` drive the token selector for `variable`. `gradient`\n * drives the gradient editor for `variable`. `literal` drives `control` and\n * reads `variable` back off the root, which is where an intrinsic lands: the\n * editors write those into the alias bucket. `config` drives `control` and\n * reads `configKey` out of the config bucket, which holds editor metadata that\n * never reaches `:root`.\n */\nexport type PersistenceShape = 'token' | 'literal' | 'opacity' | 'gradient' | 'config';\n\nexport interface PersistenceCase extends View {\n shape: PersistenceShape;\n /** The alias the value is read back from. Omitted for `config`. */\n variable?: string;\n /** Selector inside the visible variant group, for `literal` and `config`. */\n control?: string;\n /** The config-bucket key, for `config`. */\n configKey?: string;\n /** Where the persisted value must land once the page has reloaded. */\n observe: { part: string; css: string };\n}\n\nexport interface PersistenceExpectation {\n cases: PersistenceCase[];\n /** The alias Reset must return to the value the server holds for it. It has\n * to be one a case above moves, so the saved value and the value the\n * component booted with are different and Reset can be seen to choose. */\n resetVariable: string;\n}\n\nexport interface ThemeExpectation extends View {\n /** Theme slug shown in the Theme Picker, e.g. `ocean`. */\n theme: string;\n /** Variables whose root value the theme must move. */\n changed: string[];\n /**\n * Variables the theme must leave exactly as they were. Two theme JSON files\n * can name the identical design token (e.g. both `--border-neutral`) and\n * still resolve to different rendered colors, because many color primitives\n * are computed per theme from a palette/seed even when the alias string is\n * unchanged — comparing the two files' alias *names* does not establish\n * invariance. Pick `unchanged` candidates from token families a theme never\n * recolors: `--shadow-*`, `--icon-size-*`, `--font-size-*`,\n * `--line-height-*`, `--font-family`/`--font-mono`, and the spacing/radius/\n * border-width scale steps (`--space-*`, `--radius-*`, `--border-width-*`)\n * are all fixed constants, not palette-derived — verify a candidate's\n * *resolved* value is identical between the two theme files before trusting\n * it, not just that the alias name matches.\n */\n unchanged: string[];\n /** Variables the theme's own component config aliases, and the design token\n * each must resolve to under it. */\n aliasedTo: Record<string, string>;\n /** Where the theme's value must land in the rendered preview. */\n observe: { part: string; css: string; variable: string };\n}\n\nexport type InteractionAction =\n | { kind: 'press'; part: string; key: string }\n | { kind: 'click'; part: string }\n | { kind: 'type'; part: string; text: string }\n | { kind: 'dragTo'; part: string; along: string; fraction: number };\n\nexport type InteractionOutcome =\n | { kind: 'attribute'; part: string; name: string; value: string }\n | { kind: 'valueMoves'; part: string; direction: 'up' | 'down' }\n | { kind: 'valueBecomes'; part: string; value: string }\n | { kind: 'valueChanges'; part: string }\n | { kind: 'valueHolds'; part: string }\n /** Whether the part takes focus. The only outcome a button with no state\n * attribute has: a disabled one refuses it and an enabled one takes it. */\n | { kind: 'focused'; part: string; value: boolean };\n\nexport interface InteractionCase extends View {\n name: string;\n action: InteractionAction;\n expect: InteractionOutcome;\n}\n\nexport interface InteractionExpectation {\n /** The part carrying the interactive role. */\n part: string;\n role: string;\n cases: InteractionCase[];\n}\n\n/**\n * One mounted case under happy-dom: props in, a DOM event, one assertion. It\n * carries what a browser cannot reach, the callback a controlled component\n * calls instead of moving its own state.\n */\nexport interface BehaviorCase {\n name: string;\n /** Props the fixture mounts with. A snippet-valued prop is not\n * expressible; a component whose behavior needs one records that case\n * as inapplicable with the reason. */\n props?: Record<string, unknown>;\n action?: { kind: 'click' | 'keydown' | 'input'; part: string; key?: string; value?: string };\n expect:\n | { kind: 'callback'; prop: string; args: unknown[] }\n | { kind: 'no-callback'; prop: string }\n | { kind: 'attribute'; part: string; name: string; value: string | null }\n | { kind: 'text'; part: string; value: string };\n}\n\nexport interface BehaviorExpectation {\n cases: BehaviorCase[];\n}\n\nexport interface SketchPartExpectation {\n part: string;\n /** The design token `--sketch-fill` must resolve to on this part. */\n fill?: string;\n /** The design token `--sketch-stroke` must resolve to on this part. */\n stroke?: string;\n}\n\nexport interface SketchExpectation extends View {\n /** Sketchstyle id the paint is asserted under. */\n style: string;\n parts: SketchPartExpectation[];\n}\n\nexport interface ComponentContract {\n id: string;\n origin: 'system' | 'custom';\n parts: Record<string, PartDeclaration>;\n /** Part key of the component's own root element in the preview. */\n root: string;\n /** The view every obligation opens with. */\n view?: View;\n states: StateExpectation[] | Inapplicable;\n properties: PropertyExpectation[];\n /**\n * Aliases no paint map can pin, each with the reason. A token consumed\n * inside `calc()` or handed to a gradient function never appears verbatim in\n * a computed style, so the probe cannot read it back — that is the only\n * class this belongs to. A token a sibling alias merely happens to carry\n * the same rendered value through (a base padding token behind four\n * per-side overrides, say) is not in this class: the per-side entries pin\n * their own aliases, not the base one, and a paint map that drives the base\n * token directly and observes the per-side CSS property can pin it too. Put\n * that here and Wave 4's coverage JSON records the base token as covered\n * when nothing asserts the link between it and what actually painted.\n */\n uncovered?: Record<string, string>;\n persistence: PersistenceExpectation;\n theme: ThemeExpectation;\n interaction: InteractionExpectation | Inapplicable;\n /** What the component does when it is driven, proven under happy-dom. */\n behavior: BehaviorExpectation | Inapplicable;\n sketch: SketchExpectation | Inapplicable;\n}\n\n/** Roles whose components must declare interaction cases. */\nconst INTERACTIVE_ROLES = new Set([\n 'button', 'switch', 'checkbox', 'radio', 'slider', 'spinbutton', 'textbox',\n 'combobox', 'listbox', 'menuitem', 'tab', 'link', 'searchbox', 'option',\n]);\n\nexport function requiresInteraction(role: string | null): boolean {\n return role !== null && INTERACTIVE_ROLES.has(role);\n}\n\nexport function partLocator(contract: ComponentContract, key: string): PartLocator {\n const declared = contract.parts[key];\n if (declared === undefined) {\n throw new ContractViolation('contract-render', contract.id, `no part named \"${key}\" is declared`);\n }\n return typeof declared === 'string' ? { selector: declared } : declared;\n}\n"],"mappings":";AAiBO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YACW,MACA,WACT,SACA;AACA,UAAM,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,EAAE;AAJjC;AACA;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAAA,EACA;AAMb;AAOO,SAAS,eAAe,OAAuC;AACpE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAS,MAAuB,eAAe;AAC/F;AAuNA,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EAAU;AAAA,EAAU;AAAA,EAAY;AAAA,EAAS;AAAA,EAAU;AAAA,EAAc;AAAA,EACjE;AAAA,EAAY;AAAA,EAAW;AAAA,EAAY;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAa;AACjE,CAAC;AAEM,SAAS,oBAAoB,MAA8B;AAChE,SAAO,SAAS,QAAQ,kBAAkB,IAAI,IAAI;AACpD;AAEO,SAAS,YAAY,UAA6B,KAA0B;AACjF,QAAM,WAAW,SAAS,MAAM,GAAG;AACnC,MAAI,aAAa,QAAW;AAC1B,UAAM,IAAI,kBAAkB,mBAAmB,SAAS,IAAI,kBAAkB,GAAG,eAAe;AAAA,EAClG;AACA,SAAO,OAAO,aAAa,WAAW,EAAE,UAAU,SAAS,IAAI;AACjE;","names":[]}
@@ -1,9 +1,18 @@
1
+ import {
2
+ CONTRACTS_MODULE_ENV
3
+ } from "./chunk-ZMZQZ33J.js";
4
+
1
5
  // src/testing/vitest.ts
2
6
  import { mergeConfig } from "vite";
3
- var CONTRACT_INCLUDE = [import.meta.url.endsWith(".ts") ? "**/src/testing/registry.contract.ts" : "**/src/testing-js/registry.contract.js"];
7
+ var CONTRACT_INCLUDE = import.meta.url.endsWith(".ts") ? ["**/src/testing/registry.contract.ts", "**/src/testing/component-behavior.contract.ts"] : ["**/src/testing-js/registry.contract.js", "**/src/testing-js/component-behavior.contract.js"];
4
8
  var CONTRACT_EXCLUDE = ["**/dist/**", "**/dist-plugin/**", "**/.git/**"];
5
9
  function createVitestConfig(viteConfig, options = {}) {
6
10
  return mergeConfig(viteConfig, {
11
+ // `svelte`'s package exports resolve to `index-server.js` without it, whose
12
+ // `mount` throws `lifecycle_function_unavailable`, and whose
13
+ // `createEventDispatcher` is a silent no-op. A project's own vite config
14
+ // states the condition for its build, not for a test run under happy-dom.
15
+ resolve: { conditions: ["browser"] },
7
16
  test: {
8
17
  include: options.include ?? CONTRACT_INCLUDE,
9
18
  exclude: options.exclude ?? CONTRACT_EXCLUDE,
@@ -28,7 +37,12 @@ function createVitestConfig(viteConfig, options = {}) {
28
37
  // reporter's `failureMessages` to attribute a finding, so nothing here
29
38
  // can afford to be summarized away.
30
39
  chaiConfig: { truncateThreshold: 0 },
31
- ...options.registrySetup ? { env: { LIVE_TOKENS_REGISTRY_SETUP: options.registrySetup } } : {}
40
+ ...options.registrySetup || options.contractsModule ? {
41
+ env: {
42
+ ...options.registrySetup ? { LIVE_TOKENS_REGISTRY_SETUP: options.registrySetup } : {},
43
+ ...options.contractsModule ? { [CONTRACTS_MODULE_ENV]: options.contractsModule } : {}
44
+ }
45
+ } : {}
32
46
  }
33
47
  });
34
48
  }
@@ -36,4 +50,4 @@ function createVitestConfig(viteConfig, options = {}) {
36
50
  export {
37
51
  createVitestConfig
38
52
  };
39
- //# sourceMappingURL=chunk-FAFOAWYL.js.map
53
+ //# sourceMappingURL=chunk-Q3YIAAG3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../testing/vitest.ts"],"sourcesContent":["import { mergeConfig, type UserConfig } from 'vite';\nimport type { TestUserConfig } from 'vitest/config';\nimport { CONTRACTS_MODULE_ENV } from './contracts';\n\n// The barrel (`./index`) re-exports this from `./playwright`, which imports\n// `@playwright/test` at module top. A Vitest-only consumer authoring\n// `live-tokens.testing.ts` needs `defineTestingConfig` without that import\n// ever resolving, so it is reachable from here too.\nexport { defineTestingConfig, resolveTestingConfig } from './config';\nexport type { LiveTokensTestingConfig, ResolvedTestingConfig } from './config';\n\nexport interface VitestConfigOptions {\n /** Files the run collects. Default: the shipped registry and behavior\n * contracts, matched both in this package and in an installed copy. */\n include?: string[];\n exclude?: string[];\n setupFiles?: string[];\n /** Default `happy-dom`, which the registry contract needs to mount a store. */\n environment?: TestUserConfig['environment'];\n /** Module the registry contract imports to register the project's\n * components. Reaches the run as an environment variable because the\n * contract file resolves the path itself. */\n registrySetup?: string;\n /** Module `selectedContracts()` imports a project's own `ComponentContract[]`\n * from, same env-var seam as `registrySetup` and mirroring\n * `createPlaywrightConfig`'s own use of it: the behavior contract's\n * `describe.each` runs at module top, before a test can resolve anything\n * for it. */\n contractsModule?: string;\n}\n\n// Matching both extensions would double-collect each contract when a developer\n// runs build:lib without cleaning: this module's own URL is `.ts` running from\n// source and `.js` once tsup compiles it, so one set of globs follows it.\nconst CONTRACT_INCLUDE = import.meta.url.endsWith('.ts')\n ? ['**/src/testing/registry.contract.ts', '**/src/testing/component-behavior.contract.ts']\n : ['**/src/testing-js/registry.contract.js', '**/src/testing-js/component-behavior.contract.js'];\n\n/** Vitest's own default drops everything under `node_modules`, which is where\n * an installed package's contract file lives. */\nconst CONTRACT_EXCLUDE = ['**/dist/**', '**/dist-plugin/**', '**/.git/**'];\n\n/**\n * The project's Vite config plus what the registry contract needs on top.\n *\n * The package ships Svelte and TypeScript source and imports a FontAwesome\n * stylesheet. Left external, Node meets that `.css` and stops with\n * `Unknown file extension \".css\"` before a single test runs, so both are\n * inlined for Vite to transform.\n */\nexport function createVitestConfig(\n viteConfig: UserConfig,\n options: VitestConfigOptions = {},\n): UserConfig {\n return mergeConfig(viteConfig, {\n // `svelte`'s package exports resolve to `index-server.js` without it, whose\n // `mount` throws `lifecycle_function_unavailable`, and whose\n // `createEventDispatcher` is a silent no-op. A project's own vite config\n // states the condition for its build, not for a test run under happy-dom.\n resolve: { conditions: ['browser'] },\n test: {\n include: options.include ?? CONTRACT_INCLUDE,\n exclude: options.exclude ?? CONTRACT_EXCLUDE,\n ...(options.setupFiles ? { setupFiles: options.setupFiles } : {}),\n environment: options.environment ?? 'happy-dom',\n environmentOptions: {\n happyDOM: {\n settings: {\n // fontLoader injects <link rel=stylesheet> for the theme's font\n // sources, which happy-dom would fetch from Google and Typekit for\n // real. That puts the public internet on the release gate's\n // critical path; treat the load as satisfied instead.\n disableCSSFileLoading: true,\n handleDisabledFileLoadingAsSuccess: true,\n },\n },\n },\n server: { deps: { inline: [/@motion-proto\\/live-tokens/, /@fortawesome/] } },\n // Vitest's default preview truncates a failed array-equality assertion\n // to `[ Array(1) ]`, which drops `checkRegistryEntry`'s actual violation\n // text. `check-component --tests` reads that text out of the JSON\n // reporter's `failureMessages` to attribute a finding, so nothing here\n // can afford to be summarized away.\n chaiConfig: { truncateThreshold: 0 },\n ...(options.registrySetup || options.contractsModule\n ? {\n env: {\n ...(options.registrySetup ? { LIVE_TOKENS_REGISTRY_SETUP: options.registrySetup } : {}),\n ...(options.contractsModule ? { [CONTRACTS_MODULE_ENV]: options.contractsModule } : {}),\n },\n }\n : {}),\n },\n } satisfies UserConfig);\n}\n"],"mappings":";;;;;AAAA,SAAS,mBAAoC;AAkC7C,IAAM,mBAAmB,YAAY,IAAI,SAAS,KAAK,IACnD,CAAC,uCAAuC,+CAA+C,IACvF,CAAC,0CAA0C,kDAAkD;AAIjG,IAAM,mBAAmB,CAAC,cAAc,qBAAqB,YAAY;AAUlE,SAAS,mBACd,YACA,UAA+B,CAAC,GACpB;AACZ,SAAO,YAAY,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,IAK7B,SAAS,EAAE,YAAY,CAAC,SAAS,EAAE;AAAA,IACnC,MAAM;AAAA,MACJ,SAAS,QAAQ,WAAW;AAAA,MAC5B,SAAS,QAAQ,WAAW;AAAA,MAC5B,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,MAC/D,aAAa,QAAQ,eAAe;AAAA,MACpC,oBAAoB;AAAA,QAClB,UAAU;AAAA,UACR,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,YAKR,uBAAuB;AAAA,YACvB,oCAAoC;AAAA,UACtC;AAAA,QACF;AAAA,MACF;AAAA,MACA,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,8BAA8B,cAAc,EAAE,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM3E,YAAY,EAAE,mBAAmB,EAAE;AAAA,MACnC,GAAI,QAAQ,iBAAiB,QAAQ,kBACjC;AAAA,QACE,KAAK;AAAA,UACH,GAAI,QAAQ,gBAAgB,EAAE,4BAA4B,QAAQ,cAAc,IAAI,CAAC;AAAA,UACrF,GAAI,QAAQ,kBAAkB,EAAE,CAAC,oBAAoB,GAAG,QAAQ,gBAAgB,IAAI,CAAC;AAAA,QACvF;AAAA,MACF,IACA,CAAC;AAAA,IACP;AAAA,EACF,CAAsB;AACxB;","names":[]}