@sveltier/ui 0.0.0-stage → 0.1.0-alpha.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 (276) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -2
  3. package/dist/components/Alert/Alert.svelte +88 -0
  4. package/dist/components/Alert/Alert.svelte.d.ts +4 -0
  5. package/dist/components/Alert/alert.css +78 -0
  6. package/dist/components/Alert/index.d.ts +2 -0
  7. package/dist/components/Alert/index.js +1 -0
  8. package/dist/components/Alert/types.d.ts +67 -0
  9. package/dist/components/Alert/types.js +1 -0
  10. package/dist/components/Avatar/Avatar.svelte +47 -0
  11. package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
  12. package/dist/components/Avatar/avatar.css +34 -0
  13. package/dist/components/Avatar/index.d.ts +2 -0
  14. package/dist/components/Avatar/index.js +1 -0
  15. package/dist/components/Avatar/types.d.ts +47 -0
  16. package/dist/components/Avatar/types.js +1 -0
  17. package/dist/components/Badge/Badge.svelte +53 -0
  18. package/dist/components/Badge/Badge.svelte.d.ts +4 -0
  19. package/dist/components/Badge/badge.css +57 -0
  20. package/dist/components/Badge/index.d.ts +2 -0
  21. package/dist/components/Badge/index.js +1 -0
  22. package/dist/components/Badge/types.d.ts +43 -0
  23. package/dist/components/Badge/types.js +1 -0
  24. package/dist/components/Button/Button.svelte +85 -0
  25. package/dist/components/Button/Button.svelte.d.ts +4 -0
  26. package/dist/components/Button/button.css +132 -0
  27. package/dist/components/Button/index.d.ts +2 -0
  28. package/dist/components/Button/index.js +1 -0
  29. package/dist/components/Button/types.d.ts +76 -0
  30. package/dist/components/Button/types.js +1 -0
  31. package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
  32. package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
  33. package/dist/components/ButtonGroup/button-group.css +39 -0
  34. package/dist/components/ButtonGroup/index.d.ts +2 -0
  35. package/dist/components/ButtonGroup/index.js +1 -0
  36. package/dist/components/ButtonGroup/types.d.ts +48 -0
  37. package/dist/components/ButtonGroup/types.js +1 -0
  38. package/dist/components/Card/Card.svelte +53 -0
  39. package/dist/components/Card/Card.svelte.d.ts +4 -0
  40. package/dist/components/Card/card.css +62 -0
  41. package/dist/components/Card/index.d.ts +2 -0
  42. package/dist/components/Card/index.js +1 -0
  43. package/dist/components/Card/types.d.ts +56 -0
  44. package/dist/components/Card/types.js +1 -0
  45. package/dist/components/Checkbox/Checkbox.svelte +105 -0
  46. package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
  47. package/dist/components/Checkbox/checkbox.css +138 -0
  48. package/dist/components/Checkbox/index.d.ts +2 -0
  49. package/dist/components/Checkbox/index.js +1 -0
  50. package/dist/components/Checkbox/types.d.ts +76 -0
  51. package/dist/components/Checkbox/types.js +1 -0
  52. package/dist/components/Chip/Chip.svelte +109 -0
  53. package/dist/components/Chip/Chip.svelte.d.ts +4 -0
  54. package/dist/components/Chip/chip.css +101 -0
  55. package/dist/components/Chip/index.d.ts +2 -0
  56. package/dist/components/Chip/index.js +1 -0
  57. package/dist/components/Chip/types.d.ts +69 -0
  58. package/dist/components/Chip/types.js +1 -0
  59. package/dist/components/Choice/Choice.svelte +60 -0
  60. package/dist/components/Choice/Choice.svelte.d.ts +4 -0
  61. package/dist/components/Choice/choice.css +75 -0
  62. package/dist/components/Choice/types.d.ts +43 -0
  63. package/dist/components/Choice/types.js +1 -0
  64. package/dist/components/Divider/Divider.svelte +19 -0
  65. package/dist/components/Divider/Divider.svelte.d.ts +4 -0
  66. package/dist/components/Divider/divider.css +23 -0
  67. package/dist/components/Divider/index.d.ts +2 -0
  68. package/dist/components/Divider/index.js +1 -0
  69. package/dist/components/Divider/types.d.ts +10 -0
  70. package/dist/components/Divider/types.js +1 -0
  71. package/dist/components/Field/Field.svelte +95 -0
  72. package/dist/components/Field/Field.svelte.d.ts +4 -0
  73. package/dist/components/Field/field.css +210 -0
  74. package/dist/components/Field/index.d.ts +3 -0
  75. package/dist/components/Field/index.js +2 -0
  76. package/dist/components/Field/types.d.ts +109 -0
  77. package/dist/components/Field/types.js +2 -0
  78. package/dist/components/Form/Form.svelte +93 -0
  79. package/dist/components/Form/Form.svelte.d.ts +8 -0
  80. package/dist/components/Form/form.css +16 -0
  81. package/dist/components/Form/index.d.ts +2 -0
  82. package/dist/components/Form/index.js +1 -0
  83. package/dist/components/Form/types.d.ts +38 -0
  84. package/dist/components/Form/types.js +1 -0
  85. package/dist/components/Icon/Icon.svelte +38 -0
  86. package/dist/components/Icon/Icon.svelte.d.ts +4 -0
  87. package/dist/components/Icon/icon.css +33 -0
  88. package/dist/components/Icon/index.d.ts +2 -0
  89. package/dist/components/Icon/index.js +1 -0
  90. package/dist/components/Icon/types.d.ts +34 -0
  91. package/dist/components/Icon/types.js +1 -0
  92. package/dist/components/List/List.svelte +39 -0
  93. package/dist/components/List/List.svelte.d.ts +4 -0
  94. package/dist/components/List/ListItem.svelte +57 -0
  95. package/dist/components/List/ListItem.svelte.d.ts +4 -0
  96. package/dist/components/List/index.d.ts +3 -0
  97. package/dist/components/List/index.js +2 -0
  98. package/dist/components/List/list.css +127 -0
  99. package/dist/components/List/types.d.ts +67 -0
  100. package/dist/components/List/types.js +1 -0
  101. package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
  102. package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
  103. package/dist/components/NativeSelect/index.d.ts +2 -0
  104. package/dist/components/NativeSelect/index.js +1 -0
  105. package/dist/components/NativeSelect/native-select.css +45 -0
  106. package/dist/components/NativeSelect/types.d.ts +58 -0
  107. package/dist/components/NativeSelect/types.js +1 -0
  108. package/dist/components/NumberField/NumberField.svelte +246 -0
  109. package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
  110. package/dist/components/NumberField/index.d.ts +2 -0
  111. package/dist/components/NumberField/index.js +1 -0
  112. package/dist/components/NumberField/number-field.css +74 -0
  113. package/dist/components/NumberField/types.d.ts +73 -0
  114. package/dist/components/NumberField/types.js +1 -0
  115. package/dist/components/Progress/ProgressCircular.svelte +61 -0
  116. package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
  117. package/dist/components/Progress/ProgressLinear.svelte +44 -0
  118. package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
  119. package/dist/components/Progress/index.d.ts +3 -0
  120. package/dist/components/Progress/index.js +2 -0
  121. package/dist/components/Progress/progress-circular.css +61 -0
  122. package/dist/components/Progress/progress-linear.css +67 -0
  123. package/dist/components/Progress/types.d.ts +53 -0
  124. package/dist/components/Progress/types.js +1 -0
  125. package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
  126. package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
  127. package/dist/components/RadioGroup/index.d.ts +2 -0
  128. package/dist/components/RadioGroup/index.js +1 -0
  129. package/dist/components/RadioGroup/radio-group.css +139 -0
  130. package/dist/components/RadioGroup/types.d.ts +93 -0
  131. package/dist/components/RadioGroup/types.js +1 -0
  132. package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
  133. package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
  134. package/dist/components/SegmentedControl/index.d.ts +2 -0
  135. package/dist/components/SegmentedControl/index.js +1 -0
  136. package/dist/components/SegmentedControl/segmented-control.css +68 -0
  137. package/dist/components/SegmentedControl/types.d.ts +60 -0
  138. package/dist/components/SegmentedControl/types.js +1 -0
  139. package/dist/components/Skeleton/Skeleton.svelte +33 -0
  140. package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
  141. package/dist/components/Skeleton/index.d.ts +2 -0
  142. package/dist/components/Skeleton/index.js +1 -0
  143. package/dist/components/Skeleton/skeleton.css +70 -0
  144. package/dist/components/Skeleton/types.d.ts +25 -0
  145. package/dist/components/Skeleton/types.js +1 -0
  146. package/dist/components/Slider/Slider.svelte +153 -0
  147. package/dist/components/Slider/Slider.svelte.d.ts +4 -0
  148. package/dist/components/Slider/index.d.ts +2 -0
  149. package/dist/components/Slider/index.js +1 -0
  150. package/dist/components/Slider/slider.css +206 -0
  151. package/dist/components/Slider/types.d.ts +102 -0
  152. package/dist/components/Slider/types.js +1 -0
  153. package/dist/components/Switch/Switch.svelte +94 -0
  154. package/dist/components/Switch/Switch.svelte.d.ts +4 -0
  155. package/dist/components/Switch/index.d.ts +2 -0
  156. package/dist/components/Switch/index.js +1 -0
  157. package/dist/components/Switch/switch.css +118 -0
  158. package/dist/components/Switch/types.d.ts +67 -0
  159. package/dist/components/Switch/types.js +1 -0
  160. package/dist/components/Tabs/Tabs.svelte +93 -0
  161. package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
  162. package/dist/components/Tabs/index.d.ts +2 -0
  163. package/dist/components/Tabs/index.js +1 -0
  164. package/dist/components/Tabs/tabs.css +73 -0
  165. package/dist/components/Tabs/types.d.ts +59 -0
  166. package/dist/components/Tabs/types.js +1 -0
  167. package/dist/components/TextField/TextField.svelte +115 -0
  168. package/dist/components/TextField/TextField.svelte.d.ts +4 -0
  169. package/dist/components/TextField/index.d.ts +2 -0
  170. package/dist/components/TextField/index.js +1 -0
  171. package/dist/components/TextField/types.d.ts +57 -0
  172. package/dist/components/TextField/types.js +1 -0
  173. package/dist/components/Textarea/Textarea.svelte +116 -0
  174. package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
  175. package/dist/components/Textarea/index.d.ts +2 -0
  176. package/dist/components/Textarea/index.js +1 -0
  177. package/dist/components/Textarea/textarea.css +35 -0
  178. package/dist/components/Textarea/types.d.ts +60 -0
  179. package/dist/components/Textarea/types.js +1 -0
  180. package/dist/core/defaults/DefaultsProvider.svelte +30 -0
  181. package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
  182. package/dist/core/defaults/context.d.ts +45 -0
  183. package/dist/core/defaults/context.js +38 -0
  184. package/dist/core/display/display.d.ts +39 -0
  185. package/dist/core/display/display.js +53 -0
  186. package/dist/core/errors/codes.d.ts +112 -0
  187. package/dist/core/errors/codes.js +96 -0
  188. package/dist/core/errors/errors.d.ts +8 -0
  189. package/dist/core/errors/errors.js +7 -0
  190. package/dist/core/form/constraints.d.ts +15 -0
  191. package/dist/core/form/constraints.js +42 -0
  192. package/dist/core/form/context.d.ts +19 -0
  193. package/dist/core/form/context.js +4 -0
  194. package/dist/core/form/field.svelte.d.ts +34 -0
  195. package/dist/core/form/field.svelte.js +66 -0
  196. package/dist/core/form/number.d.ts +24 -0
  197. package/dist/core/form/number.js +71 -0
  198. package/dist/core/form/types.d.ts +22 -0
  199. package/dist/core/form/types.js +1 -0
  200. package/dist/core/locale/locale.d.ts +55 -0
  201. package/dist/core/locale/locale.js +80 -0
  202. package/dist/core/props/classes.d.ts +21 -0
  203. package/dist/core/props/classes.js +44 -0
  204. package/dist/core/props/element.d.ts +18 -0
  205. package/dist/core/props/element.js +1 -0
  206. package/dist/core/props/types.d.ts +47 -0
  207. package/dist/core/props/types.js +16 -0
  208. package/dist/core/provider/UIProvider.svelte +60 -0
  209. package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
  210. package/dist/core/theme/ThemeProvider.svelte +178 -0
  211. package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
  212. package/dist/core/theme/color.d.ts +23 -0
  213. package/dist/core/theme/color.js +54 -0
  214. package/dist/core/theme/context.d.ts +25 -0
  215. package/dist/core/theme/context.js +14 -0
  216. package/dist/core/theme/css.d.ts +25 -0
  217. package/dist/core/theme/css.js +69 -0
  218. package/dist/core/theme/define.d.ts +11 -0
  219. package/dist/core/theme/define.js +21 -0
  220. package/dist/core/theme/resolve.d.ts +6 -0
  221. package/dist/core/theme/resolve.js +18 -0
  222. package/dist/core/theme/theme-provider.css +13 -0
  223. package/dist/core/theme/types.d.ts +35 -0
  224. package/dist/core/theme/types.js +61 -0
  225. package/dist/dev/off.d.ts +4 -0
  226. package/dist/dev/off.js +3 -0
  227. package/dist/dev/on.d.ts +8 -0
  228. package/dist/dev/on.js +33 -0
  229. package/dist/index.d.ts +58 -0
  230. package/dist/index.js +72 -0
  231. package/dist/primitives/anchored/anchored.d.ts +39 -0
  232. package/dist/primitives/anchored/anchored.js +104 -0
  233. package/dist/primitives/anchored/fallback.d.ts +31 -0
  234. package/dist/primitives/anchored/fallback.js +73 -0
  235. package/dist/primitives/focus/focus-trap.d.ts +25 -0
  236. package/dist/primitives/focus/focus-trap.js +86 -0
  237. package/dist/primitives/focus/roving-focus.d.ts +22 -0
  238. package/dist/primitives/focus/roving-focus.js +86 -0
  239. package/dist/primitives/focus/tabbable.d.ts +9 -0
  240. package/dist/primitives/focus/tabbable.js +75 -0
  241. package/dist/primitives/inert/inert-others.d.ts +12 -0
  242. package/dist/primitives/inert/inert-others.js +44 -0
  243. package/dist/primitives/keyboard/keymap.d.ts +17 -0
  244. package/dist/primitives/keyboard/keymap.js +39 -0
  245. package/dist/primitives/keyboard/typeahead.d.ts +18 -0
  246. package/dist/primitives/keyboard/typeahead.js +50 -0
  247. package/dist/primitives/layer/dismissable.d.ts +26 -0
  248. package/dist/primitives/layer/dismissable.js +84 -0
  249. package/dist/primitives/motion/easing.d.ts +9 -0
  250. package/dist/primitives/motion/easing.js +46 -0
  251. package/dist/primitives/motion/motion.d.ts +59 -0
  252. package/dist/primitives/motion/motion.js +80 -0
  253. package/dist/primitives/portal/Portal.svelte +37 -0
  254. package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
  255. package/dist/primitives/portal/PortalContent.svelte +15 -0
  256. package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
  257. package/dist/primitives/portal/portal.css +8 -0
  258. package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
  259. package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
  260. package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
  261. package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
  262. package/dist/styles/base.css +44 -0
  263. package/dist/styles/props.css +137 -0
  264. package/dist/styles/tokens.css +84 -0
  265. package/dist/styles/visually-hidden.css +16 -0
  266. package/dist/themes/aurora.d.ts +13 -0
  267. package/dist/themes/aurora.js +103 -0
  268. package/dist/themes/heritage.d.ts +13 -0
  269. package/dist/themes/heritage.js +102 -0
  270. package/dist/themes/maison.d.ts +13 -0
  271. package/dist/themes/maison.js +99 -0
  272. package/dist/themes/milano.d.ts +13 -0
  273. package/dist/themes/milano.js +101 -0
  274. package/dist/themes/monolith.d.ts +13 -0
  275. package/dist/themes/monolith.js +101 -0
  276. package/package.json +87 -5
@@ -0,0 +1,206 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ /*
5
+ * A label row with the value at its end, the platform's range input, ticks and a hint or an
6
+ * error. The thumb is φ⁻¹·⁵ of the control height and the track a quarter of the thumb. The
7
+ * filled part ends under the thumb's centre, which travels the track less one thumb.
8
+ */
9
+ .sv-slider {
10
+ --sv-slider-thumb: calc(var(--sv-size-height) * 0.472);
11
+ --sv-slider-track: calc(var(--sv-slider-thumb) * 0.236);
12
+ --sv-slider-fill: calc(
13
+ var(--sv-slider-thumb) / 2 + var(--sv-slider-fraction) * (100% - var(--sv-slider-thumb))
14
+ );
15
+ display: flex;
16
+ flex-direction: column;
17
+ min-inline-size: 0;
18
+ color: var(--sv-color-text);
19
+ font-family: var(--sv-font-ui);
20
+ font-size: var(--sv-size-text);
21
+ line-height: var(--sv-leading-snug);
22
+ }
23
+
24
+ .sv-slider--invalid {
25
+ --sv-color: var(--sv-color-error);
26
+ }
27
+
28
+ .sv-slider__header {
29
+ display: flex;
30
+ gap: var(--sv-space-8);
31
+ align-items: baseline;
32
+ justify-content: space-between;
33
+ }
34
+
35
+ /* The theme's label voice, as a field's label. */
36
+ .sv-slider__label {
37
+ font-size: var(--sv-label-size);
38
+ font-weight: var(--sv-weight-medium);
39
+ letter-spacing: var(--sv-label-tracking);
40
+ text-transform: var(--sv-label-transform);
41
+ }
42
+
43
+ .sv-slider__value {
44
+ font-weight: var(--sv-weight-medium);
45
+ font-variant-numeric: tabular-nums;
46
+ }
47
+
48
+ /* The input is the press target, at least 24px tall. */
49
+ .sv-slider__input {
50
+ box-sizing: border-box;
51
+ inline-size: 100%;
52
+ block-size: max(1.5rem, var(--sv-size-height) * var(--sv-density) * 0.618);
53
+ margin: 0;
54
+ padding: 0;
55
+ border-radius: var(--sv-radius-sm);
56
+ background: transparent;
57
+ cursor: pointer;
58
+ appearance: none;
59
+ }
60
+
61
+ .sv-slider__input:focus-visible {
62
+ outline-color: var(--sv-color);
63
+ }
64
+
65
+ .sv-slider__input:disabled {
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ /* Chromium and Safari: the filled part is a gradient on the track. */
70
+ .sv-slider__input::-webkit-slider-runnable-track {
71
+ block-size: var(--sv-slider-track);
72
+ border-radius: var(--sv-radius-full);
73
+ background: linear-gradient(
74
+ to right,
75
+ var(--sv-color) var(--sv-slider-fill),
76
+ var(--sv-color-border-strong) var(--sv-slider-fill)
77
+ );
78
+ }
79
+
80
+ /* An attribute selector, as minifiers rewrite :dir() into language guesses. */
81
+ :where([dir='rtl']) .sv-slider__input::-webkit-slider-runnable-track {
82
+ background: linear-gradient(
83
+ to left,
84
+ var(--sv-color) var(--sv-slider-fill),
85
+ var(--sv-color-border-strong) var(--sv-slider-fill)
86
+ );
87
+ }
88
+
89
+ .sv-slider__input::-webkit-slider-thumb {
90
+ box-sizing: border-box;
91
+ inline-size: var(--sv-slider-thumb);
92
+ block-size: var(--sv-slider-thumb);
93
+ margin-block-start: calc((var(--sv-slider-track) - var(--sv-slider-thumb)) / 2);
94
+ border: calc(var(--sv-hairline) * 2) solid var(--sv-color);
95
+ border-radius: var(--sv-radius-full);
96
+ background: var(--sv-color-surface);
97
+ appearance: none;
98
+ }
99
+
100
+ /* Firefox draws the filled part itself. */
101
+ .sv-slider__input::-moz-range-track {
102
+ block-size: var(--sv-slider-track);
103
+ border-radius: var(--sv-radius-full);
104
+ background: var(--sv-color-border-strong);
105
+ }
106
+
107
+ .sv-slider__input::-moz-range-progress {
108
+ block-size: var(--sv-slider-track);
109
+ border-radius: var(--sv-radius-full);
110
+ background: var(--sv-color);
111
+ }
112
+
113
+ .sv-slider__input::-moz-range-thumb {
114
+ box-sizing: border-box;
115
+ inline-size: var(--sv-slider-thumb);
116
+ block-size: var(--sv-slider-thumb);
117
+ border: calc(var(--sv-hairline) * 2) solid var(--sv-color);
118
+ border-radius: var(--sv-radius-full);
119
+ background: var(--sv-color-surface);
120
+ }
121
+
122
+ /* Hover and press: a halo in the accent around the thumb. */
123
+ @media (hover: hover) {
124
+ .sv-slider__input:enabled:hover::-webkit-slider-thumb {
125
+ box-shadow: 0 0 0 calc(var(--sv-slider-thumb) * 0.382)
126
+ color-mix(in oklch, var(--sv-color) var(--sv-state-hover), transparent);
127
+ }
128
+
129
+ .sv-slider__input:enabled:hover::-moz-range-thumb {
130
+ box-shadow: 0 0 0 calc(var(--sv-slider-thumb) * 0.382)
131
+ color-mix(in oklch, var(--sv-color) var(--sv-state-hover), transparent);
132
+ }
133
+ }
134
+
135
+ .sv-slider__input:enabled:active::-webkit-slider-thumb {
136
+ box-shadow: 0 0 0 calc(var(--sv-slider-thumb) * 0.382)
137
+ color-mix(in oklch, var(--sv-color) var(--sv-state-press), transparent);
138
+ }
139
+
140
+ .sv-slider__input:enabled:active::-moz-range-thumb {
141
+ box-shadow: 0 0 0 calc(var(--sv-slider-thumb) * 0.382)
142
+ color-mix(in oklch, var(--sv-color) var(--sv-state-press), transparent);
143
+ }
144
+
145
+ /*
146
+ * Ticks share one grid cell, each pushed along by the same distance as the thumb's centre.
147
+ * They are as wide as nothing, so a label centres on its tick; the end labels lean inwards.
148
+ */
149
+ .sv-slider__ticks {
150
+ display: grid;
151
+ }
152
+
153
+ .sv-slider__tick {
154
+ display: flex;
155
+ flex-direction: column;
156
+ grid-area: 1 / 1;
157
+ align-items: center;
158
+ inline-size: 0;
159
+ margin-inline-start: calc(
160
+ var(--sv-slider-thumb) / 2 + var(--sv-slider-at) * (100% - var(--sv-slider-thumb))
161
+ );
162
+ }
163
+
164
+ .sv-slider__tick--start {
165
+ align-items: flex-start;
166
+ }
167
+
168
+ .sv-slider__tick--end {
169
+ align-items: flex-end;
170
+ }
171
+
172
+ .sv-slider__tick::before {
173
+ content: '';
174
+ inline-size: var(--sv-hairline);
175
+ block-size: 0.382em;
176
+ background-color: var(--sv-color-border-strong);
177
+ }
178
+
179
+ .sv-slider__tick-label {
180
+ margin-block-start: var(--sv-space-3);
181
+ color: var(--sv-color-text-muted);
182
+ font-size: var(--sv-text-xs);
183
+ white-space: nowrap;
184
+ }
185
+
186
+ /* Padding, not margin: an app's paragraph reset (p { margin: 0 }) would win over a layer. */
187
+ .sv-slider__hint,
188
+ .sv-slider__error {
189
+ margin: 0;
190
+ padding-block-start: var(--sv-space-5);
191
+ font-size: var(--sv-text-xs);
192
+ }
193
+
194
+ .sv-slider__hint {
195
+ color: var(--sv-color-text-muted);
196
+ }
197
+
198
+ .sv-slider__error {
199
+ color: var(--sv-color-error);
200
+ }
201
+
202
+ /* The hint stays legible when the slider is off: it often says why. */
203
+ .sv-slider--disabled > :is(.sv-slider__header, .sv-slider__input, .sv-slider__ticks) {
204
+ opacity: 0.382;
205
+ }
206
+ }
@@ -0,0 +1,102 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import type { FieldError, Rule, ValidateOn } from '../../core/form/types.js';
3
+ import type { ElementProps } from '../../core/props/element.js';
4
+ import type { Color, Density, Size } from '../../core/props/types.js';
5
+ /** The Slider props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
6
+ export interface SliderDefaults {
7
+ /**
8
+ * The colour of the filled part of the track and of the thumb's ring. An invalid slider uses
9
+ * `error`.
10
+ * @default 'primary'
11
+ */
12
+ color: Color;
13
+ /**
14
+ * The size of the thumb, the track and the text.
15
+ * @default 'md'
16
+ */
17
+ size: Size;
18
+ /**
19
+ * Multiplies the height of the slider's row, never the text.
20
+ * @default 'default'
21
+ */
22
+ density: Density;
23
+ /**
24
+ * Mutes the slider and disables its input.
25
+ * @default false
26
+ */
27
+ disabled: boolean;
28
+ }
29
+ declare module '../../core/defaults/context.js' {
30
+ interface ComponentDefaults {
31
+ Slider: SliderDefaults;
32
+ }
33
+ }
34
+ /** A labelled tick under a Slider's track. */
35
+ export interface SliderTick {
36
+ value: number;
37
+ /** What the tick says. Read out as the slider's value while it sits on the tick. */
38
+ label: string;
39
+ }
40
+ type SliderOwnProps = Partial<SliderDefaults> & {
41
+ /**
42
+ * The value, between `min` and `max`, on a step. Bindable.
43
+ * @default min
44
+ */
45
+ value?: number;
46
+ /** @default 0 */
47
+ min?: number;
48
+ /** @default 100 */
49
+ max?: number;
50
+ /**
51
+ * The distance between the values the slider stops at.
52
+ * @default 1
53
+ */
54
+ step?: number;
55
+ /**
56
+ * Marks under the track: `true` for every step, a list of values, or values with labels such
57
+ * as `{ value: 50, label: 'Balanced' }`.
58
+ */
59
+ ticks?: boolean | readonly number[] | readonly SliderTick[];
60
+ /**
61
+ * How the value reads, beside the label and to screen readers, for example
62
+ * `(value) => value + '%'`. Defaults to the number in the locale's format, or the label of the
63
+ * tick the slider sits on.
64
+ */
65
+ format?: (value: number) => string;
66
+ /** The visible label, linked to the input. Without it, name the slider with `aria-label`. */
67
+ label?: string;
68
+ /** Help under the slider. An error replaces it. */
69
+ hint?: string;
70
+ /**
71
+ * An error from outside the slider, such as the server's answer: a message, a list of which
72
+ * the first is shown, or `true` for an invalid state described elsewhere.
73
+ */
74
+ error?: FieldError;
75
+ /** Checks run in order on the value. The first message is shown. */
76
+ rules?: readonly Rule<number>[];
77
+ /** When the message shows. Defaults to the form's setting, else `'blur'`. */
78
+ validateOn?: ValidateOn;
79
+ /** Inline style of the slider's root element. */
80
+ style?: string | null;
81
+ };
82
+ /**
83
+ * A number picked by dragging a thumb along a track: the platform's own range input, so the
84
+ * keyboard, touch, forms and right-to-left pages work as they always do. The value shows beside
85
+ * the label. A range always holds a value, so it takes no `required`. `class` and `style` apply
86
+ * to the root; `id`, `ref`, `name` and every other attribute go to the `<input type="range">`.
87
+ *
88
+ * @example
89
+ * <Slider
90
+ * label="Risk"
91
+ * name="risk"
92
+ * step={25}
93
+ * ticks={[
94
+ * { value: 0, label: 'Careful' },
95
+ * { value: 50, label: 'Balanced' },
96
+ * { value: 100, label: 'Bold' }
97
+ * ]}
98
+ * bind:value={risk}
99
+ * />
100
+ */
101
+ export type SliderProps = ElementProps<HTMLInputElement, Omit<HTMLInputAttributes, 'type' | 'children' | 'required'>, SliderOwnProps>;
102
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ // A setting that is on or off, with its label beside it and validation. See SwitchProps.
3
+ import { untrack } from 'svelte';
4
+ import { DEV, warnUnnamed } from '#dev';
5
+ import { useDefaults } from '../../core/defaults/context.js';
6
+ import { createField } from '../../core/form/field.svelte.js';
7
+ import { useLocale } from '../../core/locale/locale.js';
8
+ import { sharedClasses } from '../../core/props/classes.js';
9
+ import Choice from '../Choice/Choice.svelte';
10
+ import './switch.css';
11
+ import type { SwitchDefaults, SwitchProps } from './types.js';
12
+
13
+ let {
14
+ color,
15
+ size,
16
+ density,
17
+ disabled,
18
+ checked = $bindable(false),
19
+ label,
20
+ hint,
21
+ error,
22
+ rules,
23
+ validateOn,
24
+ required = false,
25
+ id,
26
+ 'aria-describedby': describedBy,
27
+ class: className,
28
+ style,
29
+ ref = $bindable(null),
30
+ ...rest
31
+ }: SwitchProps = $props();
32
+
33
+ const resolve = useDefaults<SwitchDefaults>('Switch', {
34
+ color: 'primary',
35
+ size: 'md',
36
+ density: 'default',
37
+ disabled: false
38
+ });
39
+ const p = $derived(resolve({ color, size, density, disabled }));
40
+ const locale = useLocale();
41
+ // A form reset goes back to the state the switch started in.
42
+ const initial = untrack(() => checked ?? false);
43
+
44
+ const field = createField<boolean>({
45
+ value: () => checked ?? false,
46
+ // Rules run on a switch that is on only, as they run on a filled field only.
47
+ empty: (on) => !on,
48
+ check: (on) => (!on && required ? locale.t('required') : undefined),
49
+ rules: () => rules,
50
+ error: () => error,
51
+ validateOn: () => validateOn,
52
+ control: () => ref
53
+ });
54
+
55
+ if (DEV) $effect(() => warnUnnamed('Switch', ref));
56
+
57
+ // A toggle is a whole edit, so it also counts as leaving: the message shows at once, even in
58
+ // Safari, where a click does not focus the input.
59
+ function toggled() {
60
+ field.changed();
61
+ field.left();
62
+ }
63
+ </script>
64
+
65
+ <Choice
66
+ {id}
67
+ {label}
68
+ {hint}
69
+ error={field.error}
70
+ {required}
71
+ disabled={p.disabled}
72
+ class={['sv-switch', sharedClasses(p, 'Switch'), className]}
73
+ {style}
74
+ onchange={toggled}
75
+ >
76
+ {#snippet children(attributes)}
77
+ <span class="sv-switch__control">
78
+ <input
79
+ bind:this={ref}
80
+ defaultChecked={initial}
81
+ {...rest}
82
+ {...attributes}
83
+ aria-describedby={[describedBy, attributes['aria-describedby']].filter(Boolean).join(' ') ||
84
+ undefined}
85
+ type="checkbox"
86
+ role="switch"
87
+ class="sv-switch__input"
88
+ bind:checked
89
+ />
90
+ <span class="sv-switch__track" aria-hidden="true"><span class="sv-switch__thumb"></span></span
91
+ >
92
+ </span>
93
+ {/snippet}
94
+ </Choice>
@@ -0,0 +1,4 @@
1
+ import type { SwitchProps } from './types.js';
2
+ declare const Switch: import("svelte").Component<SwitchProps, {}, "ref" | "checked">;
3
+ type Switch = ReturnType<typeof Switch>;
4
+ export default Switch;
@@ -0,0 +1,2 @@
1
+ export { default as Switch } from './Switch.svelte';
2
+ export type { SwitchDefaults, SwitchProps } from './types.js';
@@ -0,0 +1 @@
1
+ export { default as Switch } from './Switch.svelte';
@@ -0,0 +1,118 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ /*
5
+ * A track as tall as a line of the label and φ times as wide, with the theme's round radius
6
+ * (square in a square theme). The thumb travels the difference.
7
+ */
8
+ .sv-switch {
9
+ --sv-switch-block: 1.272em;
10
+ --sv-switch-inline: calc(var(--sv-switch-block) * 1.618);
11
+ --sv-switch-pad: calc(var(--sv-switch-block) * 0.146);
12
+ --sv-switch-travel: calc(var(--sv-switch-inline) - var(--sv-switch-block));
13
+ --sv-choice-control: var(--sv-switch-inline);
14
+ }
15
+
16
+ /*
17
+ * The native input lies over the drawn track, invisible, so focus, keys and forms stay native.
18
+ * The track sits on the first line of the label.
19
+ */
20
+ .sv-switch__control {
21
+ position: relative;
22
+ display: inline-flex;
23
+ flex: none;
24
+ margin-block-start: calc((1lh - var(--sv-switch-block)) / 2);
25
+ }
26
+
27
+ .sv-switch__input {
28
+ position: absolute;
29
+ inset: 0;
30
+ inline-size: 100%;
31
+ block-size: 100%;
32
+ margin: 0;
33
+ opacity: 0;
34
+ cursor: inherit;
35
+ }
36
+
37
+ /* Transparent borders turn visible in forced colours, where backgrounds are dropped. */
38
+ .sv-switch__track {
39
+ position: relative;
40
+ box-sizing: border-box;
41
+ inline-size: var(--sv-switch-inline);
42
+ block-size: var(--sv-switch-block);
43
+ border: var(--sv-hairline) solid transparent;
44
+ border-radius: var(--sv-radius-full);
45
+ background-color: var(--sv-color-border-strong);
46
+ pointer-events: none;
47
+ }
48
+
49
+ /* The fill (before) fades in under the thumb; the state layer (after) sits over both. */
50
+ .sv-switch__track::before,
51
+ .sv-switch__track::after {
52
+ content: '';
53
+ position: absolute;
54
+ inset: calc(var(--sv-hairline) * -1);
55
+ border-radius: inherit;
56
+ opacity: 0;
57
+ }
58
+
59
+ .sv-switch__track::before {
60
+ background-color: var(--sv-color);
61
+ transition: opacity var(--sv-duration-fast) var(--sv-ease);
62
+ }
63
+
64
+ .sv-switch__track::after {
65
+ background-color: var(--sv-color-text);
66
+ }
67
+
68
+ .sv-switch__thumb {
69
+ position: absolute;
70
+ inset-block-start: calc(var(--sv-switch-pad) - var(--sv-hairline));
71
+ inset-inline-start: calc(var(--sv-switch-pad) - var(--sv-hairline));
72
+ box-sizing: border-box;
73
+ inline-size: calc(var(--sv-switch-block) - 2 * var(--sv-switch-pad));
74
+ block-size: calc(var(--sv-switch-block) - 2 * var(--sv-switch-pad));
75
+ border: var(--sv-hairline) solid transparent;
76
+ border-radius: var(--sv-radius-full);
77
+ background-color: var(--sv-color-surface);
78
+ transition: translate var(--sv-duration-fast) var(--sv-ease);
79
+ }
80
+
81
+ .sv-switch__input:checked + .sv-switch__track::before {
82
+ opacity: 1;
83
+ }
84
+
85
+ .sv-switch__input:checked + .sv-switch__track::after {
86
+ background-color: var(--sv-on-color);
87
+ }
88
+
89
+ .sv-switch__input:checked + .sv-switch__track .sv-switch__thumb {
90
+ translate: var(--sv-switch-travel) 0;
91
+ background-color: var(--sv-on-color);
92
+ }
93
+
94
+ /* An attribute selector, as minifiers rewrite :dir() into language guesses. */
95
+ :where([dir='rtl']) .sv-switch__input:checked + .sv-switch__track .sv-switch__thumb {
96
+ translate: calc(var(--sv-switch-travel) * -1) 0;
97
+ }
98
+
99
+ /* Hover and press mix a layer over the track. The disabled guard adds no weight. */
100
+ @media (hover: hover) {
101
+ :where(.sv-switch:not(.sv-choice--disabled)) > .sv-choice__row:hover .sv-switch__track::after {
102
+ opacity: var(--sv-state-hover);
103
+ }
104
+ }
105
+
106
+ :where(.sv-switch:not(.sv-choice--disabled)) > .sv-choice__row:active .sv-switch__track::after {
107
+ opacity: var(--sv-state-press);
108
+ }
109
+
110
+ .sv-switch__input:focus-visible + .sv-switch__track {
111
+ outline: var(--sv-focus-width) solid var(--sv-color);
112
+ outline-offset: var(--sv-focus-offset);
113
+ }
114
+
115
+ .sv-choice--invalid > .sv-choice__row .sv-switch__track {
116
+ background-color: var(--sv-color-error);
117
+ }
118
+ }
@@ -0,0 +1,67 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import type { Rule, ValidateOn } from '../../core/form/types.js';
3
+ import type { ElementProps } from '../../core/props/element.js';
4
+ import type { Color, Density, Size } from '../../core/props/types.js';
5
+ import type { ChoiceContentProps } from '../Choice/types.js';
6
+ /** The Switch props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
7
+ export interface SwitchDefaults {
8
+ /**
9
+ * The colour of the track while on. An invalid switch uses `error`.
10
+ * @default 'primary'
11
+ */
12
+ color: Color;
13
+ /**
14
+ * The size of the switch and the label text.
15
+ * @default 'md'
16
+ */
17
+ size: Size;
18
+ /**
19
+ * Multiplies the height of the row, never the text.
20
+ * @default 'default'
21
+ */
22
+ density: Density;
23
+ /**
24
+ * Mutes the switch and disables its input.
25
+ * @default false
26
+ */
27
+ disabled: boolean;
28
+ }
29
+ declare module '../../core/defaults/context.js' {
30
+ interface ComponentDefaults {
31
+ Switch: SwitchDefaults;
32
+ }
33
+ }
34
+ type SwitchOwnProps = Partial<SwitchDefaults> & ChoiceContentProps & {
35
+ /**
36
+ * Whether the switch is on. Bindable.
37
+ * @default false
38
+ */
39
+ checked?: boolean;
40
+ /**
41
+ * What the form submits under `name` while the switch is on.
42
+ * @default 'on'
43
+ */
44
+ value?: string;
45
+ /** Checks run in order after `required`, with the on state. The first message is shown. */
46
+ rules?: readonly Rule<boolean>[];
47
+ /** When the message shows. Defaults to the form's setting, else `'blur'`. */
48
+ validateOn?: ValidateOn;
49
+ /**
50
+ * The switch must be on. Adds an asterisk to the label.
51
+ * @default false
52
+ */
53
+ required?: boolean;
54
+ /** Inline style of the switch's root element. */
55
+ style?: string | null;
56
+ };
57
+ /**
58
+ * A setting that is on or off, with its label beside it: a native checkbox with
59
+ * `role="switch"`, drawn as a track and a thumb. `class` and `style` apply to the root; `id`,
60
+ * `ref`, `name` and every other attribute go to the `<input>`. Use it for a setting that takes
61
+ * effect at once; for a choice submitted with a form, use a Checkbox.
62
+ *
63
+ * @example
64
+ * <Switch label="Sign in with biometrics" bind:checked={biometrics} />
65
+ */
66
+ export type SwitchProps = ElementProps<HTMLInputElement, Omit<HTMLInputAttributes, 'type' | 'role' | 'children'>, SwitchOwnProps>;
67
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ <script lang="ts" generics="T extends string | number">
2
+ // Tabs and the selected tab's panel. See TabsProps for the props.
3
+ import { useDefaults } from '../../core/defaults/context.js';
4
+ import { sharedClasses } from '../../core/props/classes.js';
5
+ import { rovingFocus } from '../../primitives/focus/roving-focus.js';
6
+ import './tabs.css';
7
+ import type { TabsDefaults, TabsItem, TabsProps } from './types.js';
8
+
9
+ let {
10
+ color,
11
+ size,
12
+ density,
13
+ grow,
14
+ items,
15
+ value = $bindable(),
16
+ onchange,
17
+ activation = 'automatic',
18
+ panel,
19
+ id,
20
+ 'aria-label': ariaLabel,
21
+ 'aria-labelledby': ariaLabelledby,
22
+ class: className,
23
+ ref = $bindable(null),
24
+ ...rest
25
+ }: TabsProps<T> = $props();
26
+
27
+ const resolve = useDefaults<TabsDefaults>('Tabs', {
28
+ color: 'primary',
29
+ size: 'md',
30
+ density: 'default',
31
+ grow: false
32
+ });
33
+ const p = $derived(resolve({ color, size, density, grow }));
34
+ const base = $props.id();
35
+ const prefix = $derived(id ?? base);
36
+
37
+ // Without a value, the first tab that can be chosen.
38
+ const index = $derived.by(() => {
39
+ const chosen = items.findIndex((item) => item.value === value);
40
+ return chosen >= 0 ? chosen : items.findIndex((item) => !item.disabled);
41
+ });
42
+ const selected = $derived(items[index]);
43
+
44
+ function select(item: TabsItem<T>) {
45
+ if (item.disabled || item.value === selected?.value) return;
46
+ value = item.value;
47
+ onchange?.(item.value);
48
+ }
49
+ </script>
50
+
51
+ <div
52
+ bind:this={ref}
53
+ {...rest}
54
+ id={prefix}
55
+ class={['sv-tabs', sharedClasses(p, 'Tabs'), p.grow && 'sv-tabs--grow', className]}
56
+ >
57
+ <div
58
+ role="tablist"
59
+ aria-label={ariaLabel}
60
+ aria-labelledby={ariaLabelledby}
61
+ class="sv-tabs__list"
62
+ {@attach rovingFocus({ orientation: 'horizontal' })}
63
+ >
64
+ {#each items as item, i (item.value)}
65
+ <button
66
+ type="button"
67
+ role="tab"
68
+ id="{prefix}-tab-{i}"
69
+ class="sv-tabs__tab"
70
+ data-sv-item
71
+ aria-selected={i === index}
72
+ aria-controls={i === index && panel ? `${prefix}-panel` : undefined}
73
+ disabled={item.disabled}
74
+ onclick={() => select(item)}
75
+ onfocus={() => activation === 'automatic' && select(item)}
76
+ >
77
+ {item.label}
78
+ </button>
79
+ {/each}
80
+ </div>
81
+ {#if panel && selected}
82
+ <!-- Focusable, so keyboard users reach a panel with nothing focusable in it (APG). -->
83
+ <div
84
+ role="tabpanel"
85
+ id="{prefix}-panel"
86
+ aria-labelledby="{prefix}-tab-{index}"
87
+ tabindex="0"
88
+ class="sv-tabs__panel"
89
+ >
90
+ {@render panel(selected)}
91
+ </div>
92
+ {/if}
93
+ </div>