@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
@@ -38,13 +38,46 @@
38
38
  onclick?.();
39
39
  dispatch('click');
40
40
  }
41
+
42
+ let button: HTMLButtonElement;
43
+
44
+ // Each option renders alone, so the group is read from the DOM: the nearest
45
+ // radiogroup, else the parent the options share.
46
+ function groupOf(radio: HTMLButtonElement): HTMLButtonElement[] {
47
+ const scope = radio.closest('[role="radiogroup"]') ?? radio.parentElement;
48
+ return scope ? [...scope.querySelectorAll<HTMLButtonElement>('.radio-button')] : [radio];
49
+ }
50
+
51
+ // Roving tab stop: the group takes one Tab press, on the checked option.
52
+ $effect(() => {
53
+ void selected;
54
+ const group = groupOf(button);
55
+ const stop = group.find((radio) => radio.getAttribute('aria-checked') === 'true') ?? group[0];
56
+ for (const radio of group) radio.tabIndex = radio === stop ? 0 : -1;
57
+ });
58
+
59
+ const ARROW_STEPS: Record<string, number> = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
60
+
61
+ function moveSelection(event: KeyboardEvent) {
62
+ const step = ARROW_STEPS[event.key];
63
+ if (step === undefined) return;
64
+ event.preventDefault();
65
+ const group = groupOf(button);
66
+ const next = group[(group.indexOf(button) + step + group.length) % group.length];
67
+ next.focus();
68
+ next.click();
69
+ }
41
70
  </script>
42
71
 
43
72
  <button
44
73
  class="radio-button {className}"
45
74
  class:selected
75
+ role="radio"
76
+ aria-checked={selected}
46
77
  style={color ? `--radiobutton-color: ${color};` : ''}
78
+ bind:this={button}
47
79
  onclick={fireClick}
80
+ onkeydown={moveSelection}
48
81
  >
49
82
  <span class="radio-dot"><span class="radio-dot-fill"></span></span>
50
83
  <span class="radio-label">{label}</span>
@@ -53,7 +86,7 @@
53
86
  <style lang="scss">
54
87
  :global(:root) {
55
88
  /* Default */
56
- --radiobutton-default-dot-border-color: var(--border-neutral);
89
+ --radiobutton-default-dot-border-color: var(--border-neutral-medium);
57
90
  --radiobutton-default-dot-border-width: var(--border-width-2);
58
91
  --radiobutton-default-dot-fill: var(--text-secondary);
59
92
  --radiobutton-default-dot-size: var(--dot-size-0);
@@ -64,7 +97,7 @@
64
97
  --radiobutton-default-label-line-height: var(--line-height-none);
65
98
 
66
99
  /* Hover */
67
- --radiobutton-hover-dot-border-color: var(--border-neutral);
100
+ --radiobutton-hover-dot-border-color: var(--border-neutral-medium);
68
101
  --radiobutton-hover-dot-border-width: var(--border-width-2);
69
102
  --radiobutton-hover-dot-fill: var(--text-secondary);
70
103
  --radiobutton-hover-dot-size: var(--dot-size-50);
@@ -75,7 +108,7 @@
75
108
  --radiobutton-hover-label-line-height: var(--line-height-none);
76
109
 
77
110
  /* Selected */
78
- --radiobutton-selected-dot-border-color: var(--border-neutral);
111
+ --radiobutton-selected-dot-border-color: var(--border-neutral-medium);
79
112
  --radiobutton-selected-dot-border-width: var(--border-width-2);
80
113
  --radiobutton-selected-dot-fill: var(--text-secondary);
81
114
  --radiobutton-selected-dot-size: var(--dot-size-50);
@@ -59,6 +59,29 @@
59
59
  onchange?.(v);
60
60
  dispatch('change', v);
61
61
  }
62
+
63
+ const buttons: HTMLButtonElement[] = $state([]);
64
+
65
+ // Roving tab stop: the group takes one Tab press, on the checked segment.
66
+ let tabStop = $derived(
67
+ segments.find((s) => s.value === value && !s.disabled)?.value ?? segments.find((s) => !s.disabled)?.value,
68
+ );
69
+
70
+ const ARROW_STEPS: Record<string, number> = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
71
+
72
+ function moveSelection(event: KeyboardEvent, from: number) {
73
+ const step = ARROW_STEPS[event.key];
74
+ if (step === undefined) return;
75
+ event.preventDefault();
76
+ const count = segments.length;
77
+ for (let n = 1; n < count; n++) {
78
+ const i = (((from + step * n) % count) + count) % count;
79
+ if (segments[i].disabled) continue;
80
+ buttons[i].focus();
81
+ select(segments[i].value);
82
+ return;
83
+ }
84
+ }
62
85
  </script>
63
86
 
64
87
  <div
@@ -77,7 +100,10 @@
77
100
  disabled={disabled || seg.disabled}
78
101
  role="radio"
79
102
  aria-checked={value === seg.value}
103
+ tabindex={seg.value === tabStop ? 0 : -1}
104
+ bind:this={buttons[i]}
80
105
  onclick={() => select(seg.value)}
106
+ onkeydown={(event) => moveSelection(event, i)}
81
107
  >
82
108
  {#if seg.icon}<i class={seg.icon}></i>{/if}
83
109
  <span>{seg.label}</span>
@@ -96,7 +122,7 @@
96
122
 
97
123
  /* Bar (outer wrapper) */
98
124
  --segmentedcontrol-bar-surface: color-mix(in srgb, var(--surface-neutral-lowest) 75%, transparent);
99
- --segmentedcontrol-bar-border: var(--border-neutral);
125
+ --segmentedcontrol-bar-border: var(--border-neutral-medium);
100
126
  --segmentedcontrol-bar-border-width: var(--border-width-1);
101
127
  --segmentedcontrol-bar-radius: var(--radius-lg);
102
128
  --segmentedcontrol-bar-padding: var(--space-4);
@@ -138,7 +164,7 @@
138
164
  --segmentedcontrol-selected-text-font-weight: var(--font-weight-normal);
139
165
  --segmentedcontrol-selected-text-line-height: var(--line-height-normal);
140
166
  --segmentedcontrol-selected-icon: var(--text-secondary);
141
- --segmentedcontrol-selected-border: var(--border-brand);
167
+ --segmentedcontrol-selected-border: var(--border-brand-medium);
142
168
  --segmentedcontrol-selected-border-width: var(--border-width-1);
143
169
  --segmentedcontrol-selected-radius: var(--radius-md);
144
170
 
@@ -245,6 +271,7 @@
245
271
  @include themed-padding(--segmentedcontrol-option-padding, $h: 2);
246
272
  background: transparent;
247
273
  border: 0;
274
+ outline: none;
248
275
  border-radius: var(--segmentedcontrol-selected-radius);
249
276
  color: var(--_text-color);
250
277
  font-family: var(--_text-family);
@@ -308,6 +335,13 @@
308
335
  outline-offset: calc(var(--segmentedcontrol-selected-border-width) * -1);
309
336
  }
310
337
 
338
+ /* A ring outside the box, in a neutral that reads apart from the selected
339
+ option's brand outline. It is a shadow because the selected option
340
+ already owns the outline. */
341
+ .segment:focus-visible {
342
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
343
+ }
344
+
311
345
  .segment:disabled {
312
346
  --_text-color: var(--segmentedcontrol-disabled-text);
313
347
  --_text-family: var(--segmentedcontrol-disabled-text-font-family);
@@ -357,7 +357,7 @@
357
357
 
358
358
  .slidepager-stage:focus-visible {
359
359
  outline: none;
360
- box-shadow: var(--ring-focus-md);
360
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
361
361
  }
362
362
 
363
363
  .slidepager-stage img {
@@ -438,7 +438,7 @@
438
438
 
439
439
  .slidepager-control:focus-visible {
440
440
  outline: none;
441
- box-shadow: var(--ring-focus-md);
441
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
442
442
  }
443
443
 
444
444
  .slidepager-counter {
@@ -131,13 +131,13 @@
131
131
  to one moves the other until the designer unlinks it. */
132
132
  /* Single: one thumb. */
133
133
  --slider-single-track-surface: var(--surface-neutral-lowest);
134
- --slider-single-track-border: var(--border-neutral-subtle);
134
+ --slider-single-track-border: var(--border-neutral-medium);
135
135
  --slider-single-track-border-width: var(--border-width-1);
136
136
  --slider-single-track-height: var(--space-8);
137
137
  --slider-single-track-radius: var(--radius-full);
138
138
  --slider-single-fill: var(--surface-brand-high);
139
139
  --slider-single-thumb-surface: var(--surface-neutral-highest);
140
- --slider-single-thumb-border: var(--border-brand);
140
+ --slider-single-thumb-border: var(--border-brand-medium);
141
141
  --slider-single-thumb-border-width: var(--border-width-2);
142
142
  --slider-single-thumb-size: var(--font-size-lg);
143
143
  --slider-single-thumb-radius: var(--radius-full);
@@ -151,13 +151,13 @@
151
151
 
152
152
  /* Range: two thumbs bounding a fill. */
153
153
  --slider-range-track-surface: var(--surface-neutral-lowest);
154
- --slider-range-track-border: var(--border-neutral-subtle);
154
+ --slider-range-track-border: var(--border-neutral-medium);
155
155
  --slider-range-track-border-width: var(--border-width-1);
156
156
  --slider-range-track-height: var(--space-8);
157
157
  --slider-range-track-radius: var(--radius-full);
158
158
  --slider-range-fill: var(--surface-brand-high);
159
159
  --slider-range-thumb-surface: var(--surface-neutral-highest);
160
- --slider-range-thumb-border: var(--border-brand);
160
+ --slider-range-thumb-border: var(--border-brand-medium);
161
161
  --slider-range-thumb-border-width: var(--border-width-2);
162
162
  --slider-range-thumb-size: var(--font-size-lg);
163
163
  --slider-range-thumb-radius: var(--radius-full);
@@ -224,6 +224,10 @@
224
224
  margin-block: calc((var(--_thumb-size) - var(--_track-height)) / 2);
225
225
  border-style: solid;
226
226
  transition: background var(--duration-150), border-color var(--duration-150);
227
+ /* WCAG 2.2 SC 2.5.8: the native thumb takes the pointer over 24px or
228
+ more. A cap smaller than that sits up to half the difference off the
229
+ native thumb's centre at either end of the travel. */
230
+ --_hit-size: max(var(--_thumb-size), var(--space-24));
227
231
  --_origin: calc(var(--_thumb-size) / 2 - var(--_track-border-width));
228
232
  --_travel: calc(100% + var(--_track-border-width) * 2 - var(--_thumb-size));
229
233
  }
@@ -287,7 +291,7 @@
287
291
  top: 50%;
288
292
  left: calc(-1 * var(--_track-border-width));
289
293
  width: calc(100% + var(--_track-border-width) * 2);
290
- height: var(--_thumb-size);
294
+ height: var(--_hit-size);
291
295
  margin: 0;
292
296
  padding: 0;
293
297
  transform: translateY(-50%);
@@ -299,14 +303,14 @@
299
303
  .thumb:disabled { cursor: not-allowed; }
300
304
  .thumb::-webkit-slider-thumb {
301
305
  appearance: none;
302
- width: var(--_thumb-size);
303
- height: var(--_thumb-size);
306
+ width: var(--_hit-size);
307
+ height: var(--_hit-size);
304
308
  opacity: 0;
305
309
  pointer-events: auto;
306
310
  }
307
311
  .thumb::-moz-range-thumb {
308
- width: var(--_thumb-size);
309
- height: var(--_thumb-size);
312
+ width: var(--_hit-size);
313
+ height: var(--_hit-size);
310
314
  border: 0;
311
315
  opacity: 0;
312
316
  pointer-events: auto;
@@ -58,22 +58,52 @@
58
58
  dispatch('tabChange', tab.id);
59
59
  }
60
60
  }
61
+
62
+ const buttons: HTMLButtonElement[] = $state([]);
63
+
64
+ // Roving tab stop: the bar takes one Tab press, on the selected tab.
65
+ let tabStop = $derived(
66
+ tabs.find((t) => t.id === value && !t.disabled)?.id ?? tabs.find((t) => !t.disabled)?.id,
67
+ );
68
+
69
+ // Arrows move focus alone; Enter or Space selects, since a consumer's
70
+ // onchange may load the panel.
71
+ function moveFocus(event: KeyboardEvent, from: number) {
72
+ const enabled = tabs.flatMap((t, i) => (t.disabled ? [] : [i]));
73
+ const at = enabled.indexOf(from);
74
+ const to = {
75
+ ArrowRight: enabled[(at + 1) % enabled.length],
76
+ ArrowLeft: enabled[(at - 1 + enabled.length) % enabled.length],
77
+ Home: enabled[0],
78
+ End: enabled[enabled.length - 1],
79
+ }[event.key];
80
+ if (to === undefined) return;
81
+ event.preventDefault();
82
+ buttons[to].focus();
83
+ }
61
84
  </script>
62
85
 
63
86
  <div
64
87
  class="tab-bar {className}"
88
+ role="tablist"
65
89
  style:--tabbar-hover-tint-enabled={hoverTintValue}
66
90
  >
67
- {#each tabs as tab}
91
+ {#each tabs as tab, i}
68
92
  <button
69
93
  class="tab"
70
94
  class:selected={value === tab.id}
71
95
  class:icon-only={iconOnly}
96
+ role="tab"
97
+ aria-selected={value === tab.id}
98
+ aria-label={iconOnly ? tab.label : undefined}
99
+ tabindex={tab.id === tabStop ? 0 : -1}
72
100
  disabled={tab.disabled}
101
+ bind:this={buttons[i]}
73
102
  onclick={() => selectTab(tab)}
103
+ onkeydown={(event) => moveFocus(event, i)}
74
104
  >
75
105
  {#if tab.icon}
76
- <i class={tab.icon}></i>
106
+ <i class={tab.icon} aria-hidden="true"></i>
77
107
  {/if}
78
108
  {#if !iconOnly}
79
109
  <span>{tab.label}</span>
@@ -133,7 +163,7 @@
133
163
 
134
164
  /* Selected tab */
135
165
  --tabbar-selected-text: var(--text-primary);
136
- --tabbar-selected-border: var(--color-brand-500);
166
+ --tabbar-selected-border: var(--border-brand-medium);
137
167
  --tabbar-selected-surface: var(--tint-low);
138
168
  --tabbar-selected-text-font-family: var(--font-sans);
139
169
  --tabbar-selected-text-font-size: var(--font-size-md);
@@ -59,7 +59,7 @@
59
59
  :global(:root) {
60
60
  /* Default (off resting) — also carries all geometry + label typography. */
61
61
  --toggle-track-surface: var(--surface-neutral);
62
- --toggle-track-border: var(--border-neutral);
62
+ --toggle-track-border: var(--border-neutral-medium);
63
63
  --toggle-track-border-width: var(--border-width-1);
64
64
  --toggle-track-radius: var(--radius-full);
65
65
  --toggle-track-padding: var(--space-2);
@@ -78,7 +78,7 @@
78
78
 
79
79
  /* On (selected — toggle is checked). */
80
80
  --toggle-on-track-surface: var(--surface-brand-high);
81
- --toggle-on-track-border: var(--border-brand);
81
+ --toggle-on-track-border: var(--border-brand-medium);
82
82
  --toggle-on-thumb-surface: var(--text-primary);
83
83
  --toggle-on-thumb-border: var(--border-brand-strong);
84
84
 
@@ -225,7 +225,7 @@
225
225
 
226
226
  .videoframe-trigger:focus-visible {
227
227
  outline: none;
228
- box-shadow: var(--ring-focus-md);
228
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
229
229
  border-radius: var(--sketch-radius, var(--videoframe-frame-radius));
230
230
  }
231
231
 
@@ -318,7 +318,7 @@
318
318
 
319
319
  .videolightbox-trigger:focus-visible {
320
320
  outline: none;
321
- box-shadow: var(--ring-focus-md);
321
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
322
322
  }
323
323
 
324
324
  .videolightbox-trigger img,
@@ -436,7 +436,7 @@
436
436
 
437
437
  .videolightbox-close:focus-visible {
438
438
  outline: none;
439
- box-shadow: var(--ring-focus-md);
439
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
440
440
  }
441
441
 
442
442
  @media (prefers-reduced-motion: reduce) {
@@ -0,0 +1,35 @@
1
+ const TABBABLE = [
2
+ '[href]',
3
+ 'button:not([disabled])',
4
+ 'input:not([disabled]):not([type="hidden"])',
5
+ 'select:not([disabled])',
6
+ 'textarea:not([disabled])',
7
+ '[tabindex]',
8
+ ]
9
+ .map((selector) => `${selector}:not([tabindex="-1"])`)
10
+ .join(', ');
11
+
12
+ export function tabbables(container: HTMLElement): HTMLElement[] {
13
+ return [...container.querySelectorAll<HTMLElement>(TABBABLE)];
14
+ }
15
+
16
+ /** Keeps Tab and Shift+Tab cycling through `container`'s tabbable elements.
17
+ * Focus on the container itself or outside it enters at the near end.
18
+ * Returns false when the container holds none, so the caller picks where
19
+ * focus rests. */
20
+ export function trapTab(container: HTMLElement, e: KeyboardEvent): boolean {
21
+ const stops = tabbables(container);
22
+ if (stops.length === 0) return false;
23
+ const first = stops[0];
24
+ const last = stops[stops.length - 1];
25
+ const active = document.activeElement as HTMLElement | null;
26
+ const inside = active !== null && active !== container && container.contains(active);
27
+ if (e.shiftKey && (!inside || active === first)) {
28
+ e.preventDefault();
29
+ last.focus();
30
+ } else if (!e.shiftKey && (!inside || active === last)) {
31
+ e.preventDefault();
32
+ first.focus();
33
+ }
34
+ return true;
35
+ }
@@ -0,0 +1,40 @@
1
+ "Brand / base": |
2
+ - text: Brand With Icon
3
+ "Brand / colors": |
4
+ - text: Brand With Icon
5
+ "Accent / base": |
6
+ - text: Accent With Icon
7
+ "Accent / colors": |
8
+ - text: Accent With Icon
9
+ "Neutral / base": |
10
+ - text: Neutral With Icon
11
+ "Neutral / colors": |
12
+ - text: Neutral With Icon
13
+ "Alternate / base": |
14
+ - text: Alternate With Icon
15
+ "Alternate / colors": |
16
+ - text: Alternate With Icon
17
+ "Canvas / base": |
18
+ - text: Canvas With Icon
19
+ "Canvas / colors": |
20
+ - text: Canvas With Icon
21
+ "Special / base": |
22
+ - text: Special With Icon
23
+ "Special / colors": |
24
+ - text: Special With Icon
25
+ "Success / base": |
26
+ - text: Success With Icon
27
+ "Success / colors": |
28
+ - text: Success With Icon
29
+ "Warning / base": |
30
+ - text: Warning With Icon
31
+ "Warning / colors": |
32
+ - text: Warning With Icon
33
+ "Danger / base": |
34
+ - text: Danger With Icon
35
+ "Danger / colors": |
36
+ - text: Danger With Icon
37
+ "Info / base": |
38
+ - text: Info With Icon
39
+ "Info / colors": |
40
+ - text: Info With Icon
@@ -0,0 +1,88 @@
1
+ "Primary / base": |
2
+ - button "Primary"
3
+ - button "With Icon"
4
+ "Secondary / base": |
5
+ - button "Secondary"
6
+ - button "With Icon"
7
+ "Secondary / default": |
8
+ - button "Secondary"
9
+ - button "With Icon"
10
+ "Secondary / hover": |
11
+ - button "Secondary"
12
+ - button "With Icon"
13
+ "Secondary / disabled": |
14
+ - button "Secondary" [disabled]
15
+ - button "With Icon" [disabled]
16
+ "Outline / base": |
17
+ - button "Outline"
18
+ - button "With Icon"
19
+ "Outline / default": |
20
+ - button "Outline"
21
+ - button "With Icon"
22
+ "Outline / hover": |
23
+ - button "Outline"
24
+ - button "With Icon"
25
+ "Outline / disabled": |
26
+ - button "Outline" [disabled]
27
+ - button "With Icon" [disabled]
28
+ "Success / base": |
29
+ - button "Success"
30
+ - button "With Icon"
31
+ "Success / default": |
32
+ - button "Success"
33
+ - button "With Icon"
34
+ "Success / hover": |
35
+ - button "Success"
36
+ - button "With Icon"
37
+ "Success / disabled": |
38
+ - button "Success" [disabled]
39
+ - button "With Icon" [disabled]
40
+ "Danger / base": |
41
+ - button "Danger"
42
+ - button "With Icon"
43
+ "Danger / default": |
44
+ - button "Danger"
45
+ - button "With Icon"
46
+ "Danger / hover": |
47
+ - button "Danger"
48
+ - button "With Icon"
49
+ "Danger / disabled": |
50
+ - button "Danger" [disabled]
51
+ - button "With Icon" [disabled]
52
+ "Warning / base": |
53
+ - button "Warning"
54
+ - button "With Icon"
55
+ "Warning / default": |
56
+ - button "Warning"
57
+ - button "With Icon"
58
+ "Warning / hover": |
59
+ - button "Warning"
60
+ - button "With Icon"
61
+ "Warning / disabled": |
62
+ - button "Warning" [disabled]
63
+ - button "With Icon" [disabled]
64
+ "Outline / active": |
65
+ - button "Outline"
66
+ - button "With Icon"
67
+ "Primary / default": |
68
+ - button "Primary"
69
+ - button "With Icon"
70
+ "Primary / hover": |
71
+ - button "Primary"
72
+ - button "With Icon"
73
+ "Primary / disabled": |
74
+ - button "Primary" [disabled]
75
+ - button "With Icon" [disabled]
76
+ "Primary (small)": |
77
+ - button "Primary"
78
+ - button "Secondary"
79
+ - button "Outline"
80
+ - button "Success"
81
+ - button "Danger"
82
+ - button "Warning"
83
+ - button "Primary"
84
+ - button "Secondary"
85
+ - button "Outline"
86
+ - button "Success"
87
+ - button "Danger"
88
+ - button "Warning"
@@ -0,0 +1,12 @@
1
+ "Info": |
2
+ - strong: Note.
3
+ - text: This is contextual information you should know about.
4
+ "Success": |
5
+ - strong: Tip.
6
+ - text: Bookmark this page so you can return when you need it.
7
+ "Warning": |
8
+ - strong: Warning.
9
+ - text: This is a warning callout for important caveats or potential issues.
10
+ "Danger": |
11
+ - strong: Danger.
12
+ - text: This action is destructive and cannot be undone.
@@ -0,0 +1,8 @@
1
+ "preview": |
2
+ - text: Card title Content Placeholder Bare variant
3
+ "default": |
4
+ - text: Card title Content Placeholder Bare variant
5
+ "hover": |
6
+ - text: Card title Content Placeholder Bare variant
7
+ "preview (click root)": |
8
+ - text: Card title Content Placeholder Bare variant
@@ -0,0 +1,21 @@
1
+ "preview": |
2
+ - text: Preview text
3
+ - textbox "Preview text":
4
+ - /placeholder: Paste a long command or block to preview scrolling
5
+ - text: npx @motion-proto/live-tokens setup-claude
6
+ - code: npx @motion-proto/live-tokens setup-claude
7
+ - button "Copy to clipboard"
8
+ "default": |
9
+ - text: Preview text
10
+ - textbox "Preview text":
11
+ - /placeholder: Paste a long command or block to preview scrolling
12
+ - text: npx @motion-proto/live-tokens setup-claude
13
+ - code: npx @motion-proto/live-tokens setup-claude
14
+ - button "Copy to clipboard"
15
+ "hover": |
16
+ - text: Preview text
17
+ - textbox "Preview text":
18
+ - /placeholder: Paste a long command or block to preview scrolling
19
+ - text: npx @motion-proto/live-tokens setup-claude
20
+ - code: npx @motion-proto/live-tokens setup-claude
21
+ - button "Copy to clipboard"
@@ -0,0 +1,23 @@
1
+ "Container / Container": |
2
+ - button "Click to expand"
3
+ "Container / Header": |
4
+ - button "Click to expand"
5
+ "Container / Body": |
6
+ - button "Click to expand" [expanded]
7
+ - paragraph: This content is revealed when the section is open. Any content can go here.
8
+ "Chromeless / Header": |
9
+ - button "Click to expand"
10
+ "Chromeless / Body": |
11
+ - button "Click to expand" [expanded]
12
+ - paragraph: This content is revealed when the section is open. Any content can go here.
13
+ "With Hairline / Header": |
14
+ - button "Click to expand"
15
+ "With Hairline / Body": |
16
+ - button "Click to expand" [expanded]
17
+ - paragraph: This content is revealed when the section is open. Any content can go here.
18
+ "Container / Header (Hover)": |
19
+ - button "Click to expand"
20
+ "Chromeless / Header (Hover)": |
21
+ - button "Click to expand"
22
+ "With Hairline / Header (Hover)": |
23
+ - button "Click to expand"
@@ -0,0 +1,24 @@
1
+ "Brand": |
2
+ - text: Brand
3
+ "Accent": |
4
+ - text: Accent
5
+ "Neutral": |
6
+ - text: Neutral
7
+ "Alternate": |
8
+ - text: Alternate
9
+ "Canvas": |
10
+ - text: Canvas
11
+ "Special": |
12
+ - text: Special
13
+ "Success": |
14
+ - text: Success
15
+ "Warning": |
16
+ - text: Warning
17
+ "Danger": |
18
+ - text: Danger
19
+ "Info": |
20
+ - text: Info
21
+ "Brand / base": |
22
+ - text: Brand
23
+ "Brand / colors": |
24
+ - text: Brand
@@ -0,0 +1,42 @@
1
+ "preview": |
2
+ - dialog "Sample Dialog":
3
+ - heading "Sample Dialog" [level=3]
4
+ - button "Close"
5
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
6
+ - button "Cancel"
7
+ - button "Save"
8
+ "scrim": |
9
+ - dialog "Sample Dialog":
10
+ - heading "Sample Dialog" [level=3]
11
+ - button "Close"
12
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
13
+ - button "Cancel"
14
+ - button "Save"
15
+ "dialog": |
16
+ - dialog "Sample Dialog":
17
+ - heading "Sample Dialog" [level=3]
18
+ - button "Close"
19
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
20
+ - button "Cancel"
21
+ - button "Save"
22
+ "header": |
23
+ - dialog "Sample Dialog":
24
+ - heading "Sample Dialog" [level=3]
25
+ - button "Close"
26
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
27
+ - button "Cancel"
28
+ - button "Save"
29
+ "body": |
30
+ - dialog "Sample Dialog":
31
+ - heading "Sample Dialog" [level=3]
32
+ - button "Close"
33
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
34
+ - button "Cancel"
35
+ - button "Save"
36
+ "footer": |
37
+ - dialog "Sample Dialog":
38
+ - heading "Sample Dialog" [level=3]
39
+ - button "Close"
40
+ - paragraph: This is the dialog body content. It supports any slotted content including forms, lists, or other components.
41
+ - button "Cancel"
42
+ - button "Save"