@sveltier/ui 0.0.0-stage → 0.1.0-alpha.1

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 (331) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -2
  3. package/dist/components/Accordion/Accordion.svelte +74 -0
  4. package/dist/components/Accordion/Accordion.svelte.d.ts +25 -0
  5. package/dist/components/Accordion/accordion.css +88 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Accordion/index.js +1 -0
  8. package/dist/components/Accordion/types.d.ts +64 -0
  9. package/dist/components/Accordion/types.js +1 -0
  10. package/dist/components/Alert/Alert.svelte +88 -0
  11. package/dist/components/Alert/Alert.svelte.d.ts +4 -0
  12. package/dist/components/Alert/alert.css +78 -0
  13. package/dist/components/Alert/index.d.ts +2 -0
  14. package/dist/components/Alert/index.js +1 -0
  15. package/dist/components/Alert/types.d.ts +67 -0
  16. package/dist/components/Alert/types.js +1 -0
  17. package/dist/components/Avatar/Avatar.svelte +47 -0
  18. package/dist/components/Avatar/Avatar.svelte.d.ts +4 -0
  19. package/dist/components/Avatar/avatar.css +34 -0
  20. package/dist/components/Avatar/index.d.ts +2 -0
  21. package/dist/components/Avatar/index.js +1 -0
  22. package/dist/components/Avatar/types.d.ts +47 -0
  23. package/dist/components/Avatar/types.js +1 -0
  24. package/dist/components/Badge/Badge.svelte +53 -0
  25. package/dist/components/Badge/Badge.svelte.d.ts +4 -0
  26. package/dist/components/Badge/badge.css +57 -0
  27. package/dist/components/Badge/index.d.ts +2 -0
  28. package/dist/components/Badge/index.js +1 -0
  29. package/dist/components/Badge/types.d.ts +43 -0
  30. package/dist/components/Badge/types.js +1 -0
  31. package/dist/components/Button/Button.svelte +85 -0
  32. package/dist/components/Button/Button.svelte.d.ts +4 -0
  33. package/dist/components/Button/button.css +132 -0
  34. package/dist/components/Button/index.d.ts +2 -0
  35. package/dist/components/Button/index.js +1 -0
  36. package/dist/components/Button/types.d.ts +76 -0
  37. package/dist/components/Button/types.js +1 -0
  38. package/dist/components/ButtonGroup/ButtonGroup.svelte +61 -0
  39. package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +4 -0
  40. package/dist/components/ButtonGroup/button-group.css +39 -0
  41. package/dist/components/ButtonGroup/index.d.ts +2 -0
  42. package/dist/components/ButtonGroup/index.js +1 -0
  43. package/dist/components/ButtonGroup/types.d.ts +48 -0
  44. package/dist/components/ButtonGroup/types.js +1 -0
  45. package/dist/components/Card/Card.svelte +53 -0
  46. package/dist/components/Card/Card.svelte.d.ts +4 -0
  47. package/dist/components/Card/card.css +62 -0
  48. package/dist/components/Card/index.d.ts +2 -0
  49. package/dist/components/Card/index.js +1 -0
  50. package/dist/components/Card/types.d.ts +56 -0
  51. package/dist/components/Card/types.js +1 -0
  52. package/dist/components/Checkbox/Checkbox.svelte +105 -0
  53. package/dist/components/Checkbox/Checkbox.svelte.d.ts +4 -0
  54. package/dist/components/Checkbox/checkbox.css +138 -0
  55. package/dist/components/Checkbox/index.d.ts +2 -0
  56. package/dist/components/Checkbox/index.js +1 -0
  57. package/dist/components/Checkbox/types.d.ts +76 -0
  58. package/dist/components/Checkbox/types.js +1 -0
  59. package/dist/components/Chip/Chip.svelte +109 -0
  60. package/dist/components/Chip/Chip.svelte.d.ts +4 -0
  61. package/dist/components/Chip/chip.css +101 -0
  62. package/dist/components/Chip/index.d.ts +2 -0
  63. package/dist/components/Chip/index.js +1 -0
  64. package/dist/components/Chip/types.d.ts +69 -0
  65. package/dist/components/Chip/types.js +1 -0
  66. package/dist/components/Choice/Choice.svelte +60 -0
  67. package/dist/components/Choice/Choice.svelte.d.ts +4 -0
  68. package/dist/components/Choice/choice.css +75 -0
  69. package/dist/components/Choice/types.d.ts +43 -0
  70. package/dist/components/Choice/types.js +1 -0
  71. package/dist/components/Dialog/Dialog.svelte +36 -0
  72. package/dist/components/Dialog/Dialog.svelte.d.ts +4 -0
  73. package/dist/components/Dialog/index.d.ts +2 -0
  74. package/dist/components/Dialog/index.js +1 -0
  75. package/dist/components/Dialog/types.d.ts +50 -0
  76. package/dist/components/Dialog/types.js +1 -0
  77. package/dist/components/Divider/Divider.svelte +19 -0
  78. package/dist/components/Divider/Divider.svelte.d.ts +4 -0
  79. package/dist/components/Divider/divider.css +23 -0
  80. package/dist/components/Divider/index.d.ts +2 -0
  81. package/dist/components/Divider/index.js +1 -0
  82. package/dist/components/Divider/types.d.ts +10 -0
  83. package/dist/components/Divider/types.js +1 -0
  84. package/dist/components/Field/Field.svelte +95 -0
  85. package/dist/components/Field/Field.svelte.d.ts +4 -0
  86. package/dist/components/Field/field.css +210 -0
  87. package/dist/components/Field/index.d.ts +3 -0
  88. package/dist/components/Field/index.js +2 -0
  89. package/dist/components/Field/types.d.ts +109 -0
  90. package/dist/components/Field/types.js +2 -0
  91. package/dist/components/Form/Form.svelte +93 -0
  92. package/dist/components/Form/Form.svelte.d.ts +8 -0
  93. package/dist/components/Form/form.css +16 -0
  94. package/dist/components/Form/index.d.ts +2 -0
  95. package/dist/components/Form/index.js +1 -0
  96. package/dist/components/Form/types.d.ts +38 -0
  97. package/dist/components/Form/types.js +1 -0
  98. package/dist/components/Icon/Icon.svelte +38 -0
  99. package/dist/components/Icon/Icon.svelte.d.ts +4 -0
  100. package/dist/components/Icon/icon.css +33 -0
  101. package/dist/components/Icon/index.d.ts +2 -0
  102. package/dist/components/Icon/index.js +1 -0
  103. package/dist/components/Icon/types.d.ts +34 -0
  104. package/dist/components/Icon/types.js +1 -0
  105. package/dist/components/List/List.svelte +39 -0
  106. package/dist/components/List/List.svelte.d.ts +4 -0
  107. package/dist/components/List/ListItem.svelte +57 -0
  108. package/dist/components/List/ListItem.svelte.d.ts +4 -0
  109. package/dist/components/List/index.d.ts +3 -0
  110. package/dist/components/List/index.js +2 -0
  111. package/dist/components/List/list.css +127 -0
  112. package/dist/components/List/types.d.ts +67 -0
  113. package/dist/components/List/types.js +1 -0
  114. package/dist/components/Menu/Menu.svelte +195 -0
  115. package/dist/components/Menu/Menu.svelte.d.ts +4 -0
  116. package/dist/components/Menu/index.d.ts +2 -0
  117. package/dist/components/Menu/index.js +1 -0
  118. package/dist/components/Menu/menu.css +103 -0
  119. package/dist/components/Menu/types.d.ts +90 -0
  120. package/dist/components/Menu/types.js +1 -0
  121. package/dist/components/Modal/Modal.svelte +146 -0
  122. package/dist/components/Modal/Modal.svelte.d.ts +4 -0
  123. package/dist/components/Modal/modal.css +150 -0
  124. package/dist/components/Modal/types.d.ts +54 -0
  125. package/dist/components/Modal/types.js +1 -0
  126. package/dist/components/NativeSelect/NativeSelect.svelte +110 -0
  127. package/dist/components/NativeSelect/NativeSelect.svelte.d.ts +25 -0
  128. package/dist/components/NativeSelect/index.d.ts +2 -0
  129. package/dist/components/NativeSelect/index.js +1 -0
  130. package/dist/components/NativeSelect/native-select.css +45 -0
  131. package/dist/components/NativeSelect/types.d.ts +58 -0
  132. package/dist/components/NativeSelect/types.js +1 -0
  133. package/dist/components/NumberField/NumberField.svelte +246 -0
  134. package/dist/components/NumberField/NumberField.svelte.d.ts +4 -0
  135. package/dist/components/NumberField/index.d.ts +2 -0
  136. package/dist/components/NumberField/index.js +1 -0
  137. package/dist/components/NumberField/number-field.css +74 -0
  138. package/dist/components/NumberField/types.d.ts +73 -0
  139. package/dist/components/NumberField/types.js +1 -0
  140. package/dist/components/Popover/Popover.svelte +115 -0
  141. package/dist/components/Popover/Popover.svelte.d.ts +4 -0
  142. package/dist/components/Popover/index.d.ts +2 -0
  143. package/dist/components/Popover/index.js +1 -0
  144. package/dist/components/Popover/popover.css +67 -0
  145. package/dist/components/Popover/types.d.ts +83 -0
  146. package/dist/components/Popover/types.js +1 -0
  147. package/dist/components/Progress/ProgressCircular.svelte +61 -0
  148. package/dist/components/Progress/ProgressCircular.svelte.d.ts +4 -0
  149. package/dist/components/Progress/ProgressLinear.svelte +44 -0
  150. package/dist/components/Progress/ProgressLinear.svelte.d.ts +4 -0
  151. package/dist/components/Progress/index.d.ts +3 -0
  152. package/dist/components/Progress/index.js +2 -0
  153. package/dist/components/Progress/progress-circular.css +61 -0
  154. package/dist/components/Progress/progress-linear.css +67 -0
  155. package/dist/components/Progress/types.d.ts +53 -0
  156. package/dist/components/Progress/types.js +1 -0
  157. package/dist/components/RadioGroup/RadioGroup.svelte +149 -0
  158. package/dist/components/RadioGroup/RadioGroup.svelte.d.ts +25 -0
  159. package/dist/components/RadioGroup/index.d.ts +2 -0
  160. package/dist/components/RadioGroup/index.js +1 -0
  161. package/dist/components/RadioGroup/radio-group.css +139 -0
  162. package/dist/components/RadioGroup/types.d.ts +93 -0
  163. package/dist/components/RadioGroup/types.js +1 -0
  164. package/dist/components/SegmentedControl/SegmentedControl.svelte +70 -0
  165. package/dist/components/SegmentedControl/SegmentedControl.svelte.d.ts +25 -0
  166. package/dist/components/SegmentedControl/index.d.ts +2 -0
  167. package/dist/components/SegmentedControl/index.js +1 -0
  168. package/dist/components/SegmentedControl/segmented-control.css +68 -0
  169. package/dist/components/SegmentedControl/types.d.ts +60 -0
  170. package/dist/components/SegmentedControl/types.js +1 -0
  171. package/dist/components/Sheet/Sheet.svelte +56 -0
  172. package/dist/components/Sheet/Sheet.svelte.d.ts +4 -0
  173. package/dist/components/Sheet/index.d.ts +2 -0
  174. package/dist/components/Sheet/index.js +2 -0
  175. package/dist/components/Sheet/sheet.css +61 -0
  176. package/dist/components/Sheet/types.d.ts +51 -0
  177. package/dist/components/Sheet/types.js +1 -0
  178. package/dist/components/Skeleton/Skeleton.svelte +33 -0
  179. package/dist/components/Skeleton/Skeleton.svelte.d.ts +4 -0
  180. package/dist/components/Skeleton/index.d.ts +2 -0
  181. package/dist/components/Skeleton/index.js +1 -0
  182. package/dist/components/Skeleton/skeleton.css +70 -0
  183. package/dist/components/Skeleton/types.d.ts +25 -0
  184. package/dist/components/Skeleton/types.js +1 -0
  185. package/dist/components/Slider/Slider.svelte +153 -0
  186. package/dist/components/Slider/Slider.svelte.d.ts +4 -0
  187. package/dist/components/Slider/index.d.ts +2 -0
  188. package/dist/components/Slider/index.js +1 -0
  189. package/dist/components/Slider/slider.css +206 -0
  190. package/dist/components/Slider/types.d.ts +102 -0
  191. package/dist/components/Slider/types.js +1 -0
  192. package/dist/components/Switch/Switch.svelte +94 -0
  193. package/dist/components/Switch/Switch.svelte.d.ts +4 -0
  194. package/dist/components/Switch/index.d.ts +2 -0
  195. package/dist/components/Switch/index.js +1 -0
  196. package/dist/components/Switch/switch.css +118 -0
  197. package/dist/components/Switch/types.d.ts +67 -0
  198. package/dist/components/Switch/types.js +1 -0
  199. package/dist/components/Tabs/Tabs.svelte +93 -0
  200. package/dist/components/Tabs/Tabs.svelte.d.ts +25 -0
  201. package/dist/components/Tabs/index.d.ts +2 -0
  202. package/dist/components/Tabs/index.js +1 -0
  203. package/dist/components/Tabs/tabs.css +73 -0
  204. package/dist/components/Tabs/types.d.ts +59 -0
  205. package/dist/components/Tabs/types.js +1 -0
  206. package/dist/components/TextField/TextField.svelte +115 -0
  207. package/dist/components/TextField/TextField.svelte.d.ts +4 -0
  208. package/dist/components/TextField/index.d.ts +2 -0
  209. package/dist/components/TextField/index.js +1 -0
  210. package/dist/components/TextField/types.d.ts +57 -0
  211. package/dist/components/TextField/types.js +1 -0
  212. package/dist/components/Textarea/Textarea.svelte +116 -0
  213. package/dist/components/Textarea/Textarea.svelte.d.ts +4 -0
  214. package/dist/components/Textarea/index.d.ts +2 -0
  215. package/dist/components/Textarea/index.js +1 -0
  216. package/dist/components/Textarea/textarea.css +35 -0
  217. package/dist/components/Textarea/types.d.ts +60 -0
  218. package/dist/components/Textarea/types.js +1 -0
  219. package/dist/components/Tooltip/Tooltip.svelte +161 -0
  220. package/dist/components/Tooltip/Tooltip.svelte.d.ts +4 -0
  221. package/dist/components/Tooltip/index.d.ts +2 -0
  222. package/dist/components/Tooltip/index.js +1 -0
  223. package/dist/components/Tooltip/tooltip.css +44 -0
  224. package/dist/components/Tooltip/types.d.ts +55 -0
  225. package/dist/components/Tooltip/types.js +1 -0
  226. package/dist/core/defaults/DefaultsProvider.svelte +30 -0
  227. package/dist/core/defaults/DefaultsProvider.svelte.d.ts +13 -0
  228. package/dist/core/defaults/context.d.ts +45 -0
  229. package/dist/core/defaults/context.js +38 -0
  230. package/dist/core/display/display.d.ts +39 -0
  231. package/dist/core/display/display.js +53 -0
  232. package/dist/core/errors/codes.d.ts +124 -0
  233. package/dist/core/errors/codes.js +108 -0
  234. package/dist/core/errors/errors.d.ts +8 -0
  235. package/dist/core/errors/errors.js +7 -0
  236. package/dist/core/form/constraints.d.ts +15 -0
  237. package/dist/core/form/constraints.js +42 -0
  238. package/dist/core/form/context.d.ts +19 -0
  239. package/dist/core/form/context.js +4 -0
  240. package/dist/core/form/field.svelte.d.ts +34 -0
  241. package/dist/core/form/field.svelte.js +66 -0
  242. package/dist/core/form/number.d.ts +24 -0
  243. package/dist/core/form/number.js +71 -0
  244. package/dist/core/form/types.d.ts +22 -0
  245. package/dist/core/form/types.js +1 -0
  246. package/dist/core/locale/locale.d.ts +56 -0
  247. package/dist/core/locale/locale.js +82 -0
  248. package/dist/core/overlay/types.d.ts +33 -0
  249. package/dist/core/overlay/types.js +1 -0
  250. package/dist/core/props/classes.d.ts +21 -0
  251. package/dist/core/props/classes.js +44 -0
  252. package/dist/core/props/element.d.ts +18 -0
  253. package/dist/core/props/element.js +1 -0
  254. package/dist/core/props/types.d.ts +47 -0
  255. package/dist/core/props/types.js +16 -0
  256. package/dist/core/provider/UIProvider.svelte +63 -0
  257. package/dist/core/provider/UIProvider.svelte.d.ts +29 -0
  258. package/dist/core/theme/ThemeProvider.svelte +178 -0
  259. package/dist/core/theme/ThemeProvider.svelte.d.ts +25 -0
  260. package/dist/core/theme/color.d.ts +23 -0
  261. package/dist/core/theme/color.js +54 -0
  262. package/dist/core/theme/context.d.ts +25 -0
  263. package/dist/core/theme/context.js +14 -0
  264. package/dist/core/theme/css.d.ts +25 -0
  265. package/dist/core/theme/css.js +73 -0
  266. package/dist/core/theme/define.d.ts +11 -0
  267. package/dist/core/theme/define.js +21 -0
  268. package/dist/core/theme/resolve.d.ts +6 -0
  269. package/dist/core/theme/resolve.js +18 -0
  270. package/dist/core/theme/theme-provider.css +13 -0
  271. package/dist/core/theme/types.d.ts +35 -0
  272. package/dist/core/theme/types.js +61 -0
  273. package/dist/core/toast/ToastOutlet.svelte +192 -0
  274. package/dist/core/toast/ToastOutlet.svelte.d.ts +11 -0
  275. package/dist/core/toast/ToastProvider.svelte +39 -0
  276. package/dist/core/toast/ToastProvider.svelte.d.ts +19 -0
  277. package/dist/core/toast/toast.css +134 -0
  278. package/dist/core/toast/toast.svelte.d.ts +117 -0
  279. package/dist/core/toast/toast.svelte.js +133 -0
  280. package/dist/dev/off.d.ts +5 -0
  281. package/dist/dev/off.js +4 -0
  282. package/dist/dev/on.d.ts +20 -0
  283. package/dist/dev/on.js +49 -0
  284. package/dist/index.d.ts +67 -0
  285. package/dist/index.js +83 -0
  286. package/dist/primitives/anchored/anchored.d.ts +39 -0
  287. package/dist/primitives/anchored/anchored.js +104 -0
  288. package/dist/primitives/anchored/fallback.d.ts +31 -0
  289. package/dist/primitives/anchored/fallback.js +73 -0
  290. package/dist/primitives/focus/focus-trap.d.ts +25 -0
  291. package/dist/primitives/focus/focus-trap.js +86 -0
  292. package/dist/primitives/focus/roving-focus.d.ts +22 -0
  293. package/dist/primitives/focus/roving-focus.js +86 -0
  294. package/dist/primitives/focus/tabbable.d.ts +9 -0
  295. package/dist/primitives/focus/tabbable.js +75 -0
  296. package/dist/primitives/inert/inert-others.d.ts +12 -0
  297. package/dist/primitives/inert/inert-others.js +44 -0
  298. package/dist/primitives/keyboard/keymap.d.ts +17 -0
  299. package/dist/primitives/keyboard/keymap.js +39 -0
  300. package/dist/primitives/keyboard/typeahead.d.ts +18 -0
  301. package/dist/primitives/keyboard/typeahead.js +50 -0
  302. package/dist/primitives/layer/dismissable.d.ts +32 -0
  303. package/dist/primitives/layer/dismissable.js +91 -0
  304. package/dist/primitives/motion/easing.d.ts +9 -0
  305. package/dist/primitives/motion/easing.js +46 -0
  306. package/dist/primitives/motion/motion.d.ts +59 -0
  307. package/dist/primitives/motion/motion.js +80 -0
  308. package/dist/primitives/portal/Portal.svelte +37 -0
  309. package/dist/primitives/portal/Portal.svelte.d.ts +9 -0
  310. package/dist/primitives/portal/PortalContent.svelte +15 -0
  311. package/dist/primitives/portal/PortalContent.svelte.d.ts +9 -0
  312. package/dist/primitives/portal/portal.css +8 -0
  313. package/dist/primitives/scroll-lock/scroll-lock.d.ts +10 -0
  314. package/dist/primitives/scroll-lock/scroll-lock.js +33 -0
  315. package/dist/primitives/virtual/virtual-list.svelte.d.ts +65 -0
  316. package/dist/primitives/virtual/virtual-list.svelte.js +291 -0
  317. package/dist/styles/base.css +44 -0
  318. package/dist/styles/props.css +140 -0
  319. package/dist/styles/tokens.css +84 -0
  320. package/dist/styles/visually-hidden.css +16 -0
  321. package/dist/themes/aurora.d.ts +13 -0
  322. package/dist/themes/aurora.js +103 -0
  323. package/dist/themes/heritage.d.ts +13 -0
  324. package/dist/themes/heritage.js +102 -0
  325. package/dist/themes/maison.d.ts +13 -0
  326. package/dist/themes/maison.js +99 -0
  327. package/dist/themes/milano.d.ts +13 -0
  328. package/dist/themes/milano.js +101 -0
  329. package/dist/themes/monolith.d.ts +13 -0
  330. package/dist/themes/monolith.js +101 -0
  331. package/package.json +87 -5
@@ -0,0 +1,246 @@
1
+ <script lang="ts">
2
+ // A number in a Field: typed in the locale's format, stepped with the keyboard or buttons, and
3
+ // validated. See NumberFieldProps for the props.
4
+ import { untrack } from 'svelte';
5
+ import { checkValue, DEV, warnUnnamed } from '#dev';
6
+ import { useDefaults } from '../../core/defaults/context.js';
7
+ import { createField } from '../../core/form/field.svelte.js';
8
+ import { numberConstraintMessage, parseNumber } from '../../core/form/number.js';
9
+ import type { Rule } from '../../core/form/types.js';
10
+ import { useLocale } from '../../core/locale/locale.js';
11
+ import Field from '../Field/Field.svelte';
12
+ import './number-field.css';
13
+ import type { NumberFieldDefaults, NumberFieldProps } from './types.js';
14
+
15
+ let {
16
+ variant,
17
+ color,
18
+ size,
19
+ density,
20
+ rounded,
21
+ disabled,
22
+ value = $bindable(null),
23
+ min,
24
+ max,
25
+ step,
26
+ format,
27
+ controls = true,
28
+ label,
29
+ hint,
30
+ error,
31
+ rules,
32
+ validateOn,
33
+ required = false,
34
+ readonly = false,
35
+ name,
36
+ prepend,
37
+ append,
38
+ id,
39
+ 'aria-describedby': describedBy,
40
+ onkeydown,
41
+ class: className,
42
+ style,
43
+ ref = $bindable(null),
44
+ ...rest
45
+ }: NumberFieldProps = $props();
46
+
47
+ const resolve = useDefaults<NumberFieldDefaults>('NumberField', {
48
+ variant: 'outlined',
49
+ color: 'primary',
50
+ size: 'md',
51
+ density: 'default',
52
+ rounded: true,
53
+ disabled: false
54
+ });
55
+ const p = $derived(resolve({ variant, color, size, density, rounded, disabled }));
56
+ const locale = useLocale();
57
+ const uid = $props.id();
58
+ const inputId = $derived(id ?? uid);
59
+
60
+ /** The number written in the locale; empty for no number. */
61
+ const show = (number: number | null) =>
62
+ number === null || !Number.isFinite(number)
63
+ ? ''
64
+ : locale.formatNumber(number, format ?? { maximumFractionDigits: 20 });
65
+
66
+ // What the input shows, and what it means: a number, null for empty, NaN for not a number. A
67
+ // form reset goes back to the text the field started with.
68
+ const initial = untrack(() => show(value ?? null));
69
+ let text = $state(initial);
70
+ let parsed = $state<number | null>(untrack(() => value ?? null));
71
+
72
+ // A value set from outside, not typed, is written in the locale format.
73
+ $effect(() => {
74
+ const next = value ?? null;
75
+ if (
76
+ !Object.is(
77
+ next,
78
+ untrack(() => parsed)
79
+ )
80
+ ) {
81
+ parsed = next;
82
+ text = show(next);
83
+ }
84
+ });
85
+
86
+ const field = createField<number | null>({
87
+ value: () => parsed,
88
+ empty: (number) => number === null,
89
+ check: (number) => numberConstraintMessage(number, { required, min, max, step }, locale),
90
+ // Rules never see an empty or unreadable value: the checks above stop those first.
91
+ rules: () => rules as readonly Rule<number | null>[] | undefined,
92
+ error: () => error,
93
+ validateOn: () => validateOn,
94
+ control: () => ref
95
+ });
96
+
97
+ if (DEV) {
98
+ $effect(() => {
99
+ warnUnnamed('NumberField', ref);
100
+ // A currency or percent sign in the field would make the text unreadable as a number.
101
+ checkValue(
102
+ 'NumberField',
103
+ 'format.style',
104
+ format?.style,
105
+ ['decimal'],
106
+ 'Put a currency or a unit in prepend or append.'
107
+ );
108
+ });
109
+ }
110
+
111
+ const places = (number: number | undefined) => (String(number ?? '').split('.')[1] ?? '').length;
112
+ // The keys and buttons move by step, or by 1 when no step is set (and then any number is
113
+ // valid). Enough decimals for the step and its starting point, so 0.1 + 0.2 lands on 0.3.
114
+ const increment = $derived(step ?? 1);
115
+ const decimals = $derived(Math.max(places(increment), places(min)));
116
+ const inert = $derived(p.disabled || readonly);
117
+ const current = $derived(parsed !== null && Number.isFinite(parsed) ? parsed : null);
118
+
119
+ // Called for typing and for a form reset alike, so the value always follows the text.
120
+ function read(next: string) {
121
+ text = next;
122
+ const number = parseNumber(next, locale.locale);
123
+ parsed = number;
124
+ if (number === null || Number.isFinite(number)) value = number;
125
+ }
126
+
127
+ /** Sets a number the keys or buttons chose, within min and max. */
128
+ function commit(number: number) {
129
+ const bounded = Math.min(max ?? Infinity, Math.max(min ?? -Infinity, number));
130
+ const next = Number(bounded.toFixed(decimals));
131
+ parsed = next;
132
+ value = next;
133
+ text = show(next);
134
+ field.changed();
135
+ }
136
+
137
+ // Moves along the steps counted from min, as a native number input does: from a value between
138
+ // two steps, up goes to the next one and down to the previous one. Empty starts at min, or 0.
139
+ function stepBy(steps: number) {
140
+ if (inert) return;
141
+ const start = min ?? 0;
142
+ if (current === null) return commit(start);
143
+ const offset = (current - start) / increment;
144
+ const from = steps > 0 ? Math.floor(offset + 1e-9) : Math.ceil(offset - 1e-9);
145
+ commit(start + (from + steps) * increment);
146
+ }
147
+
148
+ function keydown(event: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) {
149
+ (onkeydown as ((event: KeyboardEvent) => void) | null | undefined)?.(event);
150
+ if (event.defaultPrevented || inert) return;
151
+ const moves: Record<string, (() => void) | undefined> = {
152
+ ArrowUp: () => stepBy(1),
153
+ ArrowDown: () => stepBy(-1),
154
+ PageUp: () => stepBy(10),
155
+ PageDown: () => stepBy(-10),
156
+ Home: min == null ? undefined : () => commit(min),
157
+ End: max == null ? undefined : () => commit(max)
158
+ };
159
+ const move = moves[event.key];
160
+ if (!move) return;
161
+ event.preventDefault();
162
+ move();
163
+ }
164
+
165
+ // Leaving means focus went outside the field, not to a button in it. The number is then
166
+ // written in the locale format.
167
+ function leave(event: FocusEvent) {
168
+ const root = event.currentTarget as HTMLElement;
169
+ if (root.contains(event.relatedTarget as Node | null)) return;
170
+ if (current !== null) text = show(current);
171
+ field.left();
172
+ }
173
+
174
+ // Only whole numbers, and no minus sign, can use the numeric keypad.
175
+ const integer = $derived(step != null && decimals === 0);
176
+ </script>
177
+
178
+ {#snippet end()}
179
+ {@render append?.()}
180
+ {#if controls}
181
+ <span class="sv-number-field__steps">
182
+ <button
183
+ type="button"
184
+ class="sv-number-field__step"
185
+ tabindex="-1"
186
+ aria-label={locale.t('decrease')}
187
+ aria-controls={inputId}
188
+ disabled={inert || (min != null && current !== null && current <= min)}
189
+ onpointerdown={(event) => event.preventDefault()}
190
+ onclick={() => stepBy(-1)}
191
+ >
192
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14" /></svg>
193
+ </button>
194
+ <button
195
+ type="button"
196
+ class="sv-number-field__step"
197
+ tabindex="-1"
198
+ aria-label={locale.t('increase')}
199
+ aria-controls={inputId}
200
+ disabled={inert || (max != null && current !== null && current >= max)}
201
+ onpointerdown={(event) => event.preventDefault()}
202
+ onclick={() => stepBy(1)}
203
+ >
204
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
205
+ </button>
206
+ </span>
207
+ {/if}
208
+ {/snippet}
209
+
210
+ <Field
211
+ {...p}
212
+ id={inputId}
213
+ {label}
214
+ {hint}
215
+ error={field.error}
216
+ {required}
217
+ {readonly}
218
+ {prepend}
219
+ append={controls || append ? end : undefined}
220
+ class={['sv-number-field', className]}
221
+ {style}
222
+ oninput={field.changed}
223
+ onfocusout={leave}
224
+ >
225
+ {#snippet children(attributes)}
226
+ <input
227
+ bind:this={ref}
228
+ defaultValue={initial}
229
+ {...rest}
230
+ {...attributes}
231
+ aria-describedby={[describedBy, attributes['aria-describedby']].filter(Boolean).join(' ') ||
232
+ undefined}
233
+ type="text"
234
+ role="spinbutton"
235
+ inputmode={min != null && min >= 0 && integer ? 'numeric' : 'decimal'}
236
+ autocomplete="off"
237
+ aria-valuenow={current ?? undefined}
238
+ aria-valuemin={min}
239
+ aria-valuemax={max}
240
+ aria-valuetext={current === null ? undefined : text}
241
+ bind:value={() => text, read}
242
+ onkeydown={keydown}
243
+ />
244
+ {#if name}<input type="hidden" {name} value={current ?? ''} />{/if}
245
+ {/snippet}
246
+ </Field>
@@ -0,0 +1,4 @@
1
+ import type { NumberFieldProps } from './types.js';
2
+ declare const NumberField: import("svelte").Component<NumberFieldProps, {}, "ref" | "value">;
3
+ type NumberField = ReturnType<typeof NumberField>;
4
+ export default NumberField;
@@ -0,0 +1,2 @@
1
+ export { default as NumberField } from './NumberField.svelte';
2
+ export type { NumberFieldDefaults, NumberFieldProps } from './types.js';
@@ -0,0 +1 @@
1
+ export { default as NumberField } from './NumberField.svelte';
@@ -0,0 +1,74 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ /* Digits keep one width, so numbers that change or stack line up. */
5
+ .sv-number-field .sv-field__control {
6
+ font-variant-numeric: tabular-nums;
7
+ }
8
+
9
+ .sv-number-field .sv-field__append {
10
+ align-self: stretch;
11
+ }
12
+
13
+ .sv-number-field__steps {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--sv-space-2);
17
+ margin-inline-end: calc(var(--sv-size-height) * var(--sv-density) * -0.146);
18
+ }
19
+
20
+ /* Square, φ⁻¹ of the field's height, and never under 24px (WCAG 2.5.8). */
21
+ .sv-number-field__step {
22
+ --sv-step: max(1.5rem, calc(var(--sv-size-height) * var(--sv-density) * 0.618));
23
+ position: relative;
24
+ display: grid;
25
+ place-items: center;
26
+ inline-size: var(--sv-step);
27
+ block-size: var(--sv-step);
28
+ padding: 0;
29
+ border: 0;
30
+ border-radius: var(--sv-radius-sm);
31
+ background: none;
32
+ color: var(--sv-color-text-muted);
33
+ cursor: pointer;
34
+ }
35
+
36
+ /* The state layer, as on a Button: only its opacity changes. */
37
+ .sv-number-field__step::before {
38
+ content: '';
39
+ position: absolute;
40
+ inset: 0;
41
+ border-radius: inherit;
42
+ background-color: currentColor;
43
+ opacity: 0;
44
+ transition: opacity var(--sv-duration-fast) var(--sv-ease);
45
+ }
46
+
47
+ @media (hover: hover) {
48
+ .sv-number-field__step:enabled:hover {
49
+ color: var(--sv-color-text);
50
+ }
51
+
52
+ .sv-number-field__step:enabled:hover::before {
53
+ opacity: var(--sv-state-hover);
54
+ }
55
+ }
56
+
57
+ .sv-number-field__step:enabled:active::before {
58
+ opacity: var(--sv-state-press);
59
+ }
60
+
61
+ .sv-number-field__step:disabled {
62
+ opacity: 0.382;
63
+ cursor: not-allowed;
64
+ }
65
+
66
+ .sv-number-field__step svg {
67
+ inline-size: 1.272em;
68
+ block-size: 1.272em;
69
+ fill: none;
70
+ stroke: currentColor;
71
+ stroke-width: 1.75;
72
+ stroke-linecap: round;
73
+ }
74
+ }
@@ -0,0 +1,73 @@
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 { FieldContentProps, FieldDefaults } from '../Field/types.js';
5
+ /** The NumberField props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
6
+ export type NumberFieldDefaults = FieldDefaults;
7
+ declare module '../../core/defaults/context.js' {
8
+ interface ComponentDefaults {
9
+ NumberField: NumberFieldDefaults;
10
+ }
11
+ }
12
+ type NumberFieldOwnProps = Partial<NumberFieldDefaults> & FieldContentProps & {
13
+ /** The number, or `null` when the field is empty. Bindable. */
14
+ value?: number | null;
15
+ /** The smallest value. The keys and buttons stop here; a smaller typed value is an error. */
16
+ min?: number;
17
+ /** The largest value. The keys and buttons stop here; a larger typed value is an error. */
18
+ max?: number;
19
+ /**
20
+ * How far the arrow keys and the buttons move; Page Up and Page Down move ten steps. When
21
+ * set, a typed value off the steps, counted from `min`, is an error. When not set, they move
22
+ * by 1 and any number is accepted, decimals included.
23
+ */
24
+ step?: number;
25
+ /**
26
+ * How the number is written once the user leaves the field, as `Intl.NumberFormat` options
27
+ * in the app's locale: digits only, such as `{ minimumFractionDigits: 2 }`, so the user can
28
+ * edit what they see. Put a currency or a unit in `prepend` or `append`.
29
+ * @default { maximumFractionDigits: 20 }
30
+ */
31
+ format?: Intl.NumberFormatOptions;
32
+ /**
33
+ * Shows decrease and increase buttons at the end of the field.
34
+ * @default true
35
+ */
36
+ controls?: boolean;
37
+ /** Checks run in order after the HTML constraints, with the number. The first message is shown. */
38
+ rules?: readonly Rule<number>[];
39
+ /** When the message shows. Defaults to the form's setting, else `'blur'`. */
40
+ validateOn?: ValidateOn;
41
+ /**
42
+ * The field must not be empty. Adds an asterisk to the label.
43
+ * @default false
44
+ */
45
+ required?: boolean;
46
+ /**
47
+ * The number can be read and copied but not changed.
48
+ * @default false
49
+ */
50
+ readonly?: boolean;
51
+ /** Submits the plain number, such as `1234.5`, under this name in a form. */
52
+ name?: string;
53
+ /** Inline style of the field's root element. */
54
+ style?: string | null;
55
+ };
56
+ /**
57
+ * A number typed in the app's locale, stepped with the arrow keys or the buttons, and checked
58
+ * against `min`, `max` and `step`. The field shows the number formatted, for example
59
+ * `1,234.50`, and binds and submits the plain number. `class` and `style` apply to the field;
60
+ * `id`, `ref` and other attributes go to the `<input>`.
61
+ *
62
+ * @example
63
+ * <NumberField
64
+ * label="Quantity"
65
+ * name="quantity"
66
+ * min={1}
67
+ * max={99}
68
+ * required
69
+ * bind:value={quantity}
70
+ * />
71
+ */
72
+ export type NumberFieldProps = ElementProps<HTMLInputElement, HTMLInputAttributes, NumberFieldOwnProps>;
73
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ <script lang="ts">
2
+ // A non-modal panel next to the control that opens it. See PopoverProps.
3
+ import { createAttachmentKey, type Attachment } from 'svelte/attachments';
4
+ import { checkValue, DEV, warnUnnamed } from '#dev';
5
+ import { useDefaults } from '../../core/defaults/context.js';
6
+ import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
7
+ import { sharedClasses } from '../../core/props/classes.js';
8
+ import { ALIGNS, anchored, SIDES } from '../../primitives/anchored/anchored.js';
9
+ import { tabbable } from '../../primitives/focus/tabbable.js';
10
+ import { dismissable } from '../../primitives/layer/dismissable.js';
11
+ import { pop } from '../../primitives/motion/motion.js';
12
+ import './popover.css';
13
+ import type { PopoverDefaults, PopoverProps } from './types.js';
14
+
15
+ let {
16
+ size,
17
+ density,
18
+ rounded,
19
+ trigger,
20
+ open = $bindable(false),
21
+ title,
22
+ children,
23
+ actions,
24
+ side = 'bottom',
25
+ align = 'start',
26
+ offset = 8,
27
+ persistent = false,
28
+ onclose,
29
+ id: idProp,
30
+ class: className,
31
+ ref = $bindable(null),
32
+ ...rest
33
+ }: PopoverProps = $props();
34
+
35
+ const resolve = useDefaults<PopoverDefaults>('Popover', {
36
+ size: 'md',
37
+ density: 'default',
38
+ rounded: true
39
+ });
40
+ const p = $derived(resolve({ size, density, rounded }));
41
+
42
+ if (DEV) {
43
+ $effect(() => checkValue('Popover', 'side', side, SIDES));
44
+ $effect(() => checkValue('Popover', 'align', align, ALIGNS));
45
+ }
46
+
47
+ const uid = $props.id();
48
+ const id = $derived(idProp ?? uid);
49
+
50
+ // The control that opens it: the panel is placed against it, and focus goes back to it.
51
+ let anchor = $state<HTMLElement | null>(null);
52
+ const capture: Attachment<HTMLElement> = (node) => {
53
+ anchor = node;
54
+ return () => {
55
+ if (anchor === node) anchor = null;
56
+ };
57
+ };
58
+ const ANCHOR = createAttachmentKey();
59
+ const triggerAttributes: TriggerAttributes = $derived({
60
+ 'aria-haspopup': 'dialog',
61
+ 'aria-expanded': open,
62
+ 'aria-controls': open ? id : undefined,
63
+ onclick: () => (open ? close('action') : (open = true)),
64
+ [ANCHOR]: capture
65
+ });
66
+
67
+ function close(reason: CloseReason) {
68
+ if (!open) return;
69
+ // Closed from inside, focus goes back to the control; a press or a Tab elsewhere has
70
+ // already chosen where it goes.
71
+ if (ref?.contains(document.activeElement)) anchor?.focus({ preventScroll: true });
72
+ open = false;
73
+ onclose?.(reason);
74
+ }
75
+
76
+ // In the top layer, with focus on the element marked autofocus, else the first that takes
77
+ // it, else the panel itself.
78
+ const show: Attachment<HTMLElement> = (panel) => {
79
+ panel.showPopover();
80
+ if (DEV) warnUnnamed('Popover', panel, 'a title');
81
+ const target = panel.querySelector<HTMLElement>('[autofocus]') ?? tabbable(panel)[0] ?? panel;
82
+ target.focus({ preventScroll: true });
83
+ };
84
+ </script>
85
+
86
+ {@render trigger(triggerAttributes)}
87
+
88
+ {#if open}
89
+ <div
90
+ bind:this={ref}
91
+ aria-labelledby={title ? `${id}-title` : undefined}
92
+ tabindex="-1"
93
+ {...rest}
94
+ {id}
95
+ role="dialog"
96
+ popover="manual"
97
+ class={['sv-popover', sharedClasses(p, 'Popover'), className]}
98
+ {@attach show}
99
+ {@attach anchored({ anchor, side, align, offset })}
100
+ {@attach dismissable({
101
+ onDismiss: close,
102
+ escape: !persistent,
103
+ outside: !persistent,
104
+ focusOutside: !persistent,
105
+ inside: () => [anchor]
106
+ })}
107
+ transition:pop
108
+ >
109
+ {#if title}<div class="sv-popover__title" id="{id}-title">{title}</div>{/if}
110
+ {#if children}<div class="sv-popover__body">{@render children()}</div>{/if}
111
+ {#if actions}
112
+ <div class="sv-popover__actions">{@render actions({ close: () => close('action') })}</div>
113
+ {/if}
114
+ </div>
115
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { PopoverProps } from './types.js';
2
+ declare const Popover: import("svelte").Component<PopoverProps, {}, "ref" | "open">;
3
+ type Popover = ReturnType<typeof Popover>;
4
+ export default Popover;
@@ -0,0 +1,2 @@
1
+ export { default as Popover } from './Popover.svelte';
2
+ export type { PopoverDefaults, PopoverProps } from './types.js';
@@ -0,0 +1 @@
1
+ export { default as Popover } from './Popover.svelte';
@@ -0,0 +1,67 @@
1
+ @layer sv.tokens, sv.base, sv.components, sv.utilities;
2
+
3
+ @layer sv.components {
4
+ /*
5
+ * A raised surface beside its control, as wide as its content up to its size and never wider
6
+ * or taller than the screen, scrolling inside when its content is longer.
7
+ */
8
+ .sv-popover {
9
+ --sv-popover-pad: calc(var(--sv-space-13) * var(--sv-density));
10
+ box-sizing: border-box;
11
+ display: grid;
12
+ gap: var(--sv-space-13);
13
+ inline-size: max-content;
14
+ max-inline-size: min(var(--sv-popover-width), 100vw - 2 * var(--sv-space-8));
15
+ max-block-size: calc(100dvh - 2 * var(--sv-space-8));
16
+ padding: var(--sv-popover-pad);
17
+ overflow: auto;
18
+ overscroll-behavior: contain;
19
+ border: var(--sv-hairline) solid var(--sv-color-border-soft);
20
+ border-radius: var(--sv-rounded, var(--sv-radius-lg));
21
+ outline: none;
22
+ background-color: var(--sv-color-surface);
23
+ box-shadow: var(--sv-shadow);
24
+ color: var(--sv-color-text);
25
+ font-family: var(--sv-font-ui);
26
+ font-size: var(--sv-text-sm);
27
+ line-height: var(--sv-leading-normal);
28
+ }
29
+
30
+ /* Widths on the φ series: 13, 17, 21, 27 and 34rem. */
31
+ .sv-popover.sv-size-xs {
32
+ --sv-popover-width: 13rem;
33
+ }
34
+
35
+ .sv-popover.sv-size-sm {
36
+ --sv-popover-width: 17rem;
37
+ }
38
+
39
+ .sv-popover.sv-size-md {
40
+ --sv-popover-width: 21rem;
41
+ }
42
+
43
+ .sv-popover.sv-size-lg {
44
+ --sv-popover-width: 27rem;
45
+ }
46
+
47
+ .sv-popover.sv-size-xl {
48
+ --sv-popover-width: 34rem;
49
+ }
50
+
51
+ /* The heading in the theme's display voice, a step above the body. */
52
+ .sv-popover__title {
53
+ font: var(--sv-weight-display) var(--sv-text-md) / var(--sv-leading-snug) var(--sv-font-display);
54
+ letter-spacing: var(--sv-tracking-display);
55
+ }
56
+
57
+ .sv-popover__body {
58
+ min-inline-size: 0;
59
+ }
60
+
61
+ .sv-popover__actions {
62
+ display: flex;
63
+ flex-wrap: wrap;
64
+ gap: var(--sv-space-8);
65
+ justify-content: flex-end;
66
+ }
67
+ }
@@ -0,0 +1,83 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { CloseReason, TriggerAttributes } from '../../core/overlay/types.js';
4
+ import type { ElementProps } from '../../core/props/element.js';
5
+ import type { Density, Rounded, Size } from '../../core/props/types.js';
6
+ import type { Align, Side } from '../../primitives/anchored/anchored.js';
7
+ /** The Popover props that `DefaultsProvider` and `UIProvider` can set for a subtree. */
8
+ export interface PopoverDefaults {
9
+ /**
10
+ * The widest it grows, from `xs` (13rem) to `xl` (34rem); narrower content keeps it narrower.
11
+ * @default 'md'
12
+ */
13
+ size: Size;
14
+ /**
15
+ * Multiplies the padding, never the text.
16
+ * @default 'default'
17
+ */
18
+ density: Density;
19
+ /**
20
+ * Corner rounding. `true` keeps the theme's radius for large surfaces.
21
+ * @default true
22
+ */
23
+ rounded: Rounded;
24
+ }
25
+ declare module '../../core/defaults/context.js' {
26
+ interface ComponentDefaults {
27
+ Popover: PopoverDefaults;
28
+ }
29
+ }
30
+ /**
31
+ * A panel next to the control that opens it, for content the user works in: a few fields, a
32
+ * choice, a short explanation with a link. It does not block the page: Escape, a press outside
33
+ * and focus moving out close it. Focus moves into it when it opens and back to the control when
34
+ * it closes from inside. For text that only describes a control, use a Tooltip; for a task that
35
+ * needs the whole screen, a Dialog. `class`, `style`, `ref` and other attributes go to the panel.
36
+ *
37
+ * @example
38
+ * <Popover title="Amount range">
39
+ * {#snippet trigger(attributes)}<Button {...attributes} variant="outlined">Amount</Button>{/snippet}
40
+ * <NumberField label="From" bind:value={from} />
41
+ * {#snippet actions({ close })}<Button onclick={close}>Apply</Button>{/snippet}
42
+ * </Popover>
43
+ */
44
+ export type PopoverProps = ElementProps<HTMLDivElement, Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children' | 'popover' | 'role'>, Partial<PopoverDefaults> & {
45
+ /** The control that opens and closes it. Spread the attributes it receives onto it. */
46
+ trigger: Snippet<[TriggerAttributes]>;
47
+ /**
48
+ * Whether it is open. Bindable; it sets this to `false` when it closes itself.
49
+ * @default false
50
+ */
51
+ open?: boolean;
52
+ /** A heading, which also names it. Without it, name it with `aria-label`. */
53
+ title?: string;
54
+ /** The content. */
55
+ children?: Snippet;
56
+ /** Buttons at the end. `close()` closes it with the reason `'action'`. */
57
+ actions?: Snippet<[{
58
+ close: () => void;
59
+ }]>;
60
+ /**
61
+ * The side of the control it opens on, moved to the opposite side when there is no room.
62
+ * `start` and `end` follow the writing direction.
63
+ * @default 'bottom'
64
+ */
65
+ side?: Side;
66
+ /**
67
+ * Its alignment along that side.
68
+ * @default 'start'
69
+ */
70
+ align?: Align;
71
+ /**
72
+ * The room between the control and the panel, in px.
73
+ * @default 8
74
+ */
75
+ offset?: number;
76
+ /**
77
+ * Escape, a press outside and focus moving out do not close it: an answer is needed.
78
+ * @default false
79
+ */
80
+ persistent?: boolean;
81
+ /** Called when it closes itself, with the reason. */
82
+ onclose?: (reason: CloseReason) => void;
83
+ }>;
@@ -0,0 +1 @@
1
+ export {};