@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,53 @@
1
+ import { getContext, hasContext, setContext } from 'svelte';
2
+ import { MediaQuery } from 'svelte/reactivity';
3
+ import { sveltierError } from '../errors/errors.js';
4
+ /** Breakpoints, from the narrowest viewport to the widest. */
5
+ export const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
6
+ const DEFAULT_THRESHOLDS = { sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 };
7
+ /**
8
+ * Builds the breakpoint flags on one `MediaQuery` per threshold. Each flag reads only the
9
+ * queries it needs, so a component is updated only when its own threshold is crossed.
10
+ */
11
+ export function createDisplay({ thresholds: custom, mobileBreakpoint = 'md', ssrWidth = 0 } = {}) {
12
+ const thresholds = { ...DEFAULT_THRESHOLDS, ...custom };
13
+ const widths = BREAKPOINTS.slice(1).map((name) => thresholds[name]);
14
+ if (widths.some((width, i) => i > 0 && width <= widths[i - 1])) {
15
+ throw sveltierError('SV2001', `Display thresholds must increase from sm to xxl; got ${widths.join(', ')}.`);
16
+ }
17
+ const queries = widths.map((min) => new MediaQuery(`(min-width: ${min}px)`, ssrWidth >= min));
18
+ // Whether the viewport is at breakpoint `i` or wider; every viewport is at least xs.
19
+ const atLeast = (i) => i === 0 || (queries[i - 1]?.current ?? false);
20
+ const mobile = BREAKPOINTS.indexOf(mobileBreakpoint);
21
+ const display = {
22
+ get name() {
23
+ let i = queries.length;
24
+ while (i > 0 && !atLeast(i))
25
+ i--;
26
+ return BREAKPOINTS[i];
27
+ },
28
+ get mobile() {
29
+ return !atLeast(mobile);
30
+ },
31
+ thresholds
32
+ };
33
+ BREAKPOINTS.forEach((name, i) => {
34
+ const define = (key, get) => Object.defineProperty(display, key, { get, enumerable: true });
35
+ define(name, () => atLeast(i) && !atLeast(i + 1));
36
+ if (i > 0 && i < queries.length) {
37
+ define(`${name}AndUp`, () => atLeast(i));
38
+ define(`${name}AndDown`, () => !atLeast(i + 1));
39
+ }
40
+ });
41
+ return display;
42
+ }
43
+ const KEY = Symbol('sveltier.display');
44
+ let shared;
45
+ /** Gives a subtree its display instance, built from the root's options. */
46
+ export const setDisplayContext = (display) => setContext(KEY, display);
47
+ /**
48
+ * Returns the viewport breakpoints. Call it during component initialisation. Without a
49
+ * provider it returns one shared instance with the default options.
50
+ */
51
+ export function useDisplay() {
52
+ return hasContext(KEY) ? getContext(KEY) : (shared ??= createDisplay());
53
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * What an error code means: shown on the docs' errors page and in llms-full.txt. Code is marked
3
+ * with backticks, as in Markdown.
4
+ */
5
+ export interface ErrorEntry {
6
+ /** `error` is thrown in every build; `warning` is logged in development only. */
7
+ kind: 'error' | 'warning';
8
+ /** The problem in a few words. */
9
+ title: string;
10
+ /** Why the library refuses. */
11
+ cause: string;
12
+ /** What to change. */
13
+ fix: string;
14
+ }
15
+ /**
16
+ * Every error Sveltier throws or warns with, by stable code. Thrown messages start with
17
+ * `[sveltier SVxxxx]` and end with a link to the code's entry in the docs. A code keeps its
18
+ * meaning forever; its words may improve. Codes group by area: 1xxx themes, 2xxx the core
19
+ * engine, 3xxx primitives, 4xxx components, 5xxx forms.
20
+ *
21
+ * @example
22
+ * import { ERROR_CODES } from '@sveltier/ui';
23
+ * ERROR_CODES.SV1001.fix; // "Pass at least one theme pack: …"
24
+ */
25
+ export declare const ERROR_CODES: {
26
+ readonly SV1001: {
27
+ readonly kind: "error";
28
+ readonly title: "The root ThemeProvider has no themes";
29
+ readonly cause: "The outermost `<ThemeProvider>`, which `<UIProvider>` renders, registers the themes an app can show. None were passed, so there is nothing to paint.";
30
+ readonly fix: "Pass at least one theme pack: `themes={monolith}` from `'@sveltier/ui/themes/monolith'`, or several at once: `themes={{ ...monolith, ...maison }}`.";
31
+ };
32
+ readonly SV1002: {
33
+ readonly kind: "error";
34
+ readonly title: "Unknown theme";
35
+ readonly cause: "A theme was chosen by a name that is not registered: through the `theme` prop, `useTheme().set()`, or the `light`, `dark` or `fallback` options of `themeCss()`.";
36
+ readonly fix: "Use a registered name, such as `\"monolith-light\"`, or `\"system\"` to follow the operating system. To offer another theme, register it in the `themes` of the root provider.";
37
+ };
38
+ readonly SV1003: {
39
+ readonly kind: "error";
40
+ readonly title: "Invalid colour or variable name";
41
+ readonly cause: "The colours and variables of a theme become CSS custom properties, so their names must be valid identifiers.";
42
+ readonly fix: "Start the name with a letter and use only letters, digits, `-` and `_`, for example `brand` or `radius-card`.";
43
+ };
44
+ readonly SV1004: {
45
+ readonly kind: "error";
46
+ readonly title: "Unsafe theme value";
47
+ readonly cause: "Theme values are written into a `<style>` element. A value containing `<`, `>`, `{`, `}` or `;` could end the rule or the element early.";
48
+ readonly fix: "Use a plain CSS value, such as `oklch(0.62 0.19 255)` or `8px`.";
49
+ };
50
+ readonly SV1005: {
51
+ readonly kind: "error";
52
+ readonly title: "Invalid theme name";
53
+ readonly cause: "Theme names are written into the `data-sv-theme` attribute and its CSS selectors.";
54
+ readonly fix: "Start the name with a letter and use only letters, digits, `-` and `_`. `\"system\"` is reserved for following the operating system.";
55
+ };
56
+ readonly SV1006: {
57
+ readonly kind: "error";
58
+ readonly title: "Invalid nonce";
59
+ readonly cause: "The nonce is copied into the attributes of the theme's `<style>` and `<script>`, so it must be a plain base64 value.";
60
+ readonly fix: "Pass the nonce of your Content Security Policy as it is, for example SvelteKit's `%sveltekit.nonce%`.";
61
+ };
62
+ readonly SV1007: {
63
+ readonly kind: "error";
64
+ readonly title: "useTheme() outside a ThemeProvider";
65
+ readonly cause: "`useTheme()` reads the theme from the component tree, and there is no `<UIProvider>` or `<ThemeProvider>` above the component that called it.";
66
+ readonly fix: "Wrap the app in `<UIProvider>` in the root layout, and call `useTheme()` while a component inside it initialises.";
67
+ };
68
+ readonly SV2001: {
69
+ readonly kind: "error";
70
+ readonly title: "Display thresholds out of order";
71
+ readonly cause: "Breakpoints are minimum widths, so each one must be wider than the one before it.";
72
+ readonly fix: "Give widths that increase from `sm` to `xxl`, for example `{ sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 }`.";
73
+ };
74
+ readonly SV2002: {
75
+ readonly kind: "warning";
76
+ readonly title: "Invalid prop value";
77
+ readonly cause: "A prop that takes one of a fixed set of values got another one, for example `variant=\"primary\"` on a `Button`. Types catch this when the code is checked; the warning catches values that get past them, such as data from an API.";
78
+ readonly fix: "Use one of the values the message lists. The type of the prop and the props table of the component list them too.";
79
+ };
80
+ readonly SV3001: {
81
+ readonly kind: "error";
82
+ readonly title: "Portal target not found";
83
+ readonly cause: "A Portal moves its content into its target, and no element matched the target selector when the Portal mounted.";
84
+ readonly fix: "Render the target before the Portal, correct the selector, or pass the element itself.";
85
+ };
86
+ readonly SV3002: {
87
+ readonly kind: "error";
88
+ readonly title: "Invalid item size";
89
+ readonly cause: "`virtualList` places items by their sizes, so every size must be a positive number of pixels.";
90
+ readonly fix: "Pass a positive number, or a function that returns one for every index. For items whose size is unknown, pass an estimate and measure them with `{@attach list.measure}`.";
91
+ };
92
+ readonly SV3003: {
93
+ readonly kind: "error";
94
+ readonly title: "Measured item without an index";
95
+ readonly cause: "`list.measure` reads the index of each measured element from its `data-index` attribute.";
96
+ readonly fix: "Add `data-index={item.index}` to every element that has `{@attach list.measure}`.";
97
+ };
98
+ readonly SV4001: {
99
+ readonly kind: "error";
100
+ readonly title: "Closable chip that is also a button or a link";
101
+ readonly cause: "A closable chip contains a close button, and a button cannot be placed inside another button or a link.";
102
+ readonly fix: "Keep the chip `closable` and put its action in a `Button` next to it, or remove `closable`.";
103
+ };
104
+ readonly SV4002: {
105
+ readonly kind: "warning";
106
+ readonly title: "Tooltip on a control without an accessible name";
107
+ readonly cause: "A tooltip describes the control it is on; it does not name it. A control with no text, `aria-label` or `aria-labelledby`, such as an icon-only button, is announced without a name.";
108
+ readonly fix: "Give the control `aria-label`, as every icon-only button needs, or visible text. The tooltip can then say more than the name.";
109
+ };
110
+ readonly SV4003: {
111
+ readonly kind: "error";
112
+ readonly title: "useToast() outside a UIProvider";
113
+ readonly cause: "`useToast()` reads the toasts from the component tree, and there is no `<UIProvider>` or `<ToastProvider>` above the component that called it.";
114
+ readonly fix: "Wrap the app in `<UIProvider>` in the root layout, or in `<ToastProvider>` when it uses `<ThemeProvider>` alone, and call `useToast()` while a component inside it initialises.";
115
+ };
116
+ readonly SV5001: {
117
+ readonly kind: "warning";
118
+ readonly title: "Field or dialog without an accessible name";
119
+ readonly cause: "Assistive technology announces a form control or a dialog by its name, and this one has no label, title, `aria-label` or `aria-labelledby`.";
120
+ readonly fix: "Give a field a `label` and a dialog a `title`, or name it with `aria-label`, or with `aria-labelledby` when its name is visible elsewhere.";
121
+ };
122
+ };
123
+ /** A stable error code, such as `'SV1001'`. */
124
+ export type ErrorCode = keyof typeof ERROR_CODES;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Every error Sveltier throws or warns with, by stable code. Thrown messages start with
3
+ * `[sveltier SVxxxx]` and end with a link to the code's entry in the docs. A code keeps its
4
+ * meaning forever; its words may improve. Codes group by area: 1xxx themes, 2xxx the core
5
+ * engine, 3xxx primitives, 4xxx components, 5xxx forms.
6
+ *
7
+ * @example
8
+ * import { ERROR_CODES } from '@sveltier/ui';
9
+ * ERROR_CODES.SV1001.fix; // "Pass at least one theme pack: …"
10
+ */
11
+ export const ERROR_CODES = {
12
+ SV1001: {
13
+ kind: 'error',
14
+ title: 'The root ThemeProvider has no themes',
15
+ cause: 'The outermost `<ThemeProvider>`, which `<UIProvider>` renders, registers the themes an app can show. None were passed, so there is nothing to paint.',
16
+ fix: "Pass at least one theme pack: `themes={monolith}` from `'@sveltier/ui/themes/monolith'`, or several at once: `themes={{ ...monolith, ...maison }}`."
17
+ },
18
+ SV1002: {
19
+ kind: 'error',
20
+ title: 'Unknown theme',
21
+ cause: 'A theme was chosen by a name that is not registered: through the `theme` prop, `useTheme().set()`, or the `light`, `dark` or `fallback` options of `themeCss()`.',
22
+ fix: 'Use a registered name, such as `"monolith-light"`, or `"system"` to follow the operating system. To offer another theme, register it in the `themes` of the root provider.'
23
+ },
24
+ SV1003: {
25
+ kind: 'error',
26
+ title: 'Invalid colour or variable name',
27
+ cause: 'The colours and variables of a theme become CSS custom properties, so their names must be valid identifiers.',
28
+ fix: 'Start the name with a letter and use only letters, digits, `-` and `_`, for example `brand` or `radius-card`.'
29
+ },
30
+ SV1004: {
31
+ kind: 'error',
32
+ title: 'Unsafe theme value',
33
+ cause: 'Theme values are written into a `<style>` element. A value containing `<`, `>`, `{`, `}` or `;` could end the rule or the element early.',
34
+ fix: 'Use a plain CSS value, such as `oklch(0.62 0.19 255)` or `8px`.'
35
+ },
36
+ SV1005: {
37
+ kind: 'error',
38
+ title: 'Invalid theme name',
39
+ cause: 'Theme names are written into the `data-sv-theme` attribute and its CSS selectors.',
40
+ fix: 'Start the name with a letter and use only letters, digits, `-` and `_`. `"system"` is reserved for following the operating system.'
41
+ },
42
+ SV1006: {
43
+ kind: 'error',
44
+ title: 'Invalid nonce',
45
+ cause: "The nonce is copied into the attributes of the theme's `<style>` and `<script>`, so it must be a plain base64 value.",
46
+ fix: "Pass the nonce of your Content Security Policy as it is, for example SvelteKit's `%sveltekit.nonce%`."
47
+ },
48
+ SV1007: {
49
+ kind: 'error',
50
+ title: 'useTheme() outside a ThemeProvider',
51
+ cause: '`useTheme()` reads the theme from the component tree, and there is no `<UIProvider>` or `<ThemeProvider>` above the component that called it.',
52
+ fix: 'Wrap the app in `<UIProvider>` in the root layout, and call `useTheme()` while a component inside it initialises.'
53
+ },
54
+ SV2001: {
55
+ kind: 'error',
56
+ title: 'Display thresholds out of order',
57
+ cause: 'Breakpoints are minimum widths, so each one must be wider than the one before it.',
58
+ fix: 'Give widths that increase from `sm` to `xxl`, for example `{ sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 }`.'
59
+ },
60
+ SV2002: {
61
+ kind: 'warning',
62
+ title: 'Invalid prop value',
63
+ cause: 'A prop that takes one of a fixed set of values got another one, for example `variant="primary"` on a `Button`. Types catch this when the code is checked; the warning catches values that get past them, such as data from an API.',
64
+ fix: 'Use one of the values the message lists. The type of the prop and the props table of the component list them too.'
65
+ },
66
+ SV3001: {
67
+ kind: 'error',
68
+ title: 'Portal target not found',
69
+ cause: 'A Portal moves its content into its target, and no element matched the target selector when the Portal mounted.',
70
+ fix: 'Render the target before the Portal, correct the selector, or pass the element itself.'
71
+ },
72
+ SV3002: {
73
+ kind: 'error',
74
+ title: 'Invalid item size',
75
+ cause: '`virtualList` places items by their sizes, so every size must be a positive number of pixels.',
76
+ fix: 'Pass a positive number, or a function that returns one for every index. For items whose size is unknown, pass an estimate and measure them with `{@attach list.measure}`.'
77
+ },
78
+ SV3003: {
79
+ kind: 'error',
80
+ title: 'Measured item without an index',
81
+ cause: '`list.measure` reads the index of each measured element from its `data-index` attribute.',
82
+ fix: 'Add `data-index={item.index}` to every element that has `{@attach list.measure}`.'
83
+ },
84
+ SV4001: {
85
+ kind: 'error',
86
+ title: 'Closable chip that is also a button or a link',
87
+ cause: 'A closable chip contains a close button, and a button cannot be placed inside another button or a link.',
88
+ fix: 'Keep the chip `closable` and put its action in a `Button` next to it, or remove `closable`.'
89
+ },
90
+ SV4002: {
91
+ kind: 'warning',
92
+ title: 'Tooltip on a control without an accessible name',
93
+ cause: 'A tooltip describes the control it is on; it does not name it. A control with no text, `aria-label` or `aria-labelledby`, such as an icon-only button, is announced without a name.',
94
+ fix: 'Give the control `aria-label`, as every icon-only button needs, or visible text. The tooltip can then say more than the name.'
95
+ },
96
+ SV4003: {
97
+ kind: 'error',
98
+ title: 'useToast() outside a UIProvider',
99
+ cause: '`useToast()` reads the toasts from the component tree, and there is no `<UIProvider>` or `<ToastProvider>` above the component that called it.',
100
+ fix: 'Wrap the app in `<UIProvider>` in the root layout, or in `<ToastProvider>` when it uses `<ThemeProvider>` alone, and call `useToast()` while a component inside it initialises.'
101
+ },
102
+ SV5001: {
103
+ kind: 'warning',
104
+ title: 'Field or dialog without an accessible name',
105
+ cause: 'Assistive technology announces a form control or a dialog by its name, and this one has no label, title, `aria-label` or `aria-labelledby`.',
106
+ fix: 'Give a field a `label` and a dialog a `title`, or name it with `aria-label`, or with `aria-labelledby` when its name is visible elsewhere.'
107
+ }
108
+ };
@@ -0,0 +1,8 @@
1
+ import type { ErrorCode } from './codes.js';
2
+ /** The docs page that explains every error code, each under its own anchor. */
3
+ export declare const ERRORS_URL = "https://sveltier.com/errors";
4
+ /**
5
+ * An error of the library: `[sveltier SV4001] What happened and what to do. <link>`. The
6
+ * message carries the values involved; the linked entry explains the code.
7
+ */
8
+ export declare const sveltierError: (code: ErrorCode, message: string) => Error;
@@ -0,0 +1,7 @@
1
+ /** The docs page that explains every error code, each under its own anchor. */
2
+ export const ERRORS_URL = 'https://sveltier.com/errors';
3
+ /**
4
+ * An error of the library: `[sveltier SV4001] What happened and what to do. <link>`. The
5
+ * message carries the values involved; the linked entry explains the code.
6
+ */
7
+ export const sveltierError = (code, message) => new Error(`[sveltier ${code}] ${message} ${ERRORS_URL}#${code}`);
@@ -0,0 +1,15 @@
1
+ import type { LocaleInstance } from '../locale/locale.js';
2
+ /** The HTML constraints of a text control. */
3
+ export interface TextConstraints {
4
+ required?: boolean | null;
5
+ minlength?: number | null;
6
+ maxlength?: number | null;
7
+ pattern?: string | null;
8
+ type?: string | null;
9
+ }
10
+ /**
11
+ * The message for the first HTML constraint that `value` breaks, worded by the locale. As in
12
+ * HTML, an empty value can only break `required`. Lengths are counted here rather than read
13
+ * from `validity`, which browsers only update after typing.
14
+ */
15
+ export declare function textConstraintMessage(value: string, constraints: TextConstraints, t: LocaleInstance['t']): string | undefined;
@@ -0,0 +1,42 @@
1
+ // The valid e-mail address of the HTML specification.
2
+ const EMAIL = /^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*$/i;
3
+ // Compiled the way browsers compile the pattern attribute. Keys are the app's own patterns, not
4
+ // user input, so the cache is safe to share across server requests. An invalid pattern checks
5
+ // nothing, as in browsers.
6
+ const patterns = new Map();
7
+ function compile(pattern) {
8
+ let regexp = patterns.get(pattern);
9
+ if (regexp === undefined) {
10
+ try {
11
+ regexp = new RegExp(`^(?:${pattern})$`, 'v');
12
+ }
13
+ catch {
14
+ regexp = null;
15
+ }
16
+ patterns.set(pattern, regexp);
17
+ }
18
+ return regexp;
19
+ }
20
+ /**
21
+ * The message for the first HTML constraint that `value` breaks, worded by the locale. As in
22
+ * HTML, an empty value can only break `required`. Lengths are counted here rather than read
23
+ * from `validity`, which browsers only update after typing.
24
+ */
25
+ export function textConstraintMessage(value, constraints, t) {
26
+ const { required, minlength, maxlength, pattern, type } = constraints;
27
+ if (value === '')
28
+ return required ? t('required') : undefined;
29
+ if (minlength != null && value.length < Number(minlength)) {
30
+ return t('tooShort', { min: Number(minlength) });
31
+ }
32
+ if (maxlength != null && maxlength >= 0 && value.length > Number(maxlength)) {
33
+ return t('tooLong', { max: Number(maxlength) });
34
+ }
35
+ if (type === 'email' && !EMAIL.test(value))
36
+ return t('typeEmail');
37
+ if (type === 'url' && !URL.canParse(value))
38
+ return t('typeUrl');
39
+ if (pattern && compile(pattern)?.test(value) === false)
40
+ return t('patternMismatch');
41
+ return undefined;
42
+ }
@@ -0,0 +1,19 @@
1
+ import type { ValidateOn } from './types.js';
2
+ /** What a field gives the form it is in. */
3
+ export interface FormField {
4
+ /** Shows the field's message; `true` when its value passes its own checks. */
5
+ validate(): boolean;
6
+ /** Hides the message and forgets that the user changed the value. */
7
+ resetValidation(): void;
8
+ /** The control, focused when it is the first invalid one. */
9
+ control(): HTMLElement | null | undefined;
10
+ }
11
+ /** What a `Form` gives its fields. */
12
+ export interface FormContext {
13
+ /** When fields without their own `validateOn` show their messages. */
14
+ readonly validateOn: ValidateOn | undefined;
15
+ /** Adds a field; returns the function that removes it. */
16
+ register(field: FormField): () => void;
17
+ }
18
+ export declare const getFormContext: () => FormContext | undefined;
19
+ export declare const setFormContext: (context: FormContext) => FormContext;
@@ -0,0 +1,4 @@
1
+ import { getContext, hasContext, setContext } from 'svelte';
2
+ const KEY = Symbol('sveltier.form');
3
+ export const getFormContext = () => hasContext(KEY) ? getContext(KEY) : undefined;
4
+ export const setFormContext = (context) => setContext(KEY, context);
@@ -0,0 +1,34 @@
1
+ import type { FieldError, Rule, ValidateOn } from './types.js';
2
+ type Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
3
+ export interface FieldOptions<T> {
4
+ /** The current value. */
5
+ value: () => T;
6
+ /** Whether a value counts as empty. Rules do not run on an empty value. */
7
+ empty: (value: T) => boolean;
8
+ /** The control's own checks (`required` and the HTML constraints): a message or undefined. */
9
+ check: (value: T) => string | undefined;
10
+ rules: () => readonly Rule<T>[] | null | undefined;
11
+ error: () => FieldError | undefined;
12
+ validateOn: () => ValidateOn | null | undefined;
13
+ /** The native control, kept in step with `setCustomValidity()`. */
14
+ control: () => Control | null | undefined;
15
+ }
16
+ export interface FieldState {
17
+ /**
18
+ * What the field shows: the outside error, else its own message once shown; `true` for an
19
+ * invalid state without a message, `false` when valid.
20
+ */
21
+ readonly error: string | boolean;
22
+ /** Call when the user changes the value. */
23
+ changed(): void;
24
+ /** Call when focus leaves the field. */
25
+ left(): void;
26
+ }
27
+ /** A FieldError as a message, `true` for invalid without one, or `false`. */
28
+ export declare function readError(error: FieldError | undefined): string | boolean;
29
+ /**
30
+ * The validation of one field. Its message is always derived from the current value; the
31
+ * timing (`validateOn`) only decides when it is shown. Call during component initialisation.
32
+ */
33
+ export declare function createField<T>(options: FieldOptions<T>): FieldState;
34
+ export {};
@@ -0,0 +1,66 @@
1
+ import { getFormContext } from './context.js';
2
+ /** A FieldError as a message, `true` for invalid without one, or `false`. */
3
+ export function readError(error) {
4
+ if (typeof error === 'string')
5
+ return error || false;
6
+ if (Array.isArray(error))
7
+ return error[0] || false;
8
+ return error === true;
9
+ }
10
+ /**
11
+ * The validation of one field. Its message is always derived from the current value; the
12
+ * timing (`validateOn`) only decides when it is shown. Call during component initialisation.
13
+ */
14
+ export function createField(options) {
15
+ const form = getFormContext();
16
+ let shown = $state(false);
17
+ let dirty = false;
18
+ // First problem: the control's own checks, then the rules, which skip empty values.
19
+ const problem = $derived.by(() => {
20
+ const value = options.value();
21
+ const own = options.check(value);
22
+ if (own !== undefined || options.empty(value))
23
+ return own;
24
+ for (const rule of options.rules() ?? []) {
25
+ const result = rule(value);
26
+ if (result !== true)
27
+ return result;
28
+ }
29
+ return undefined;
30
+ });
31
+ const outside = $derived(readError(options.error()));
32
+ const mode = $derived(options.validateOn() ?? form?.validateOn ?? 'blur');
33
+ // A plain <form> refuses to submit with the same words. Errors from outside are left out,
34
+ // so a server's answer never blocks submitting again.
35
+ $effect(() => {
36
+ options.control()?.setCustomValidity(problem ?? '');
37
+ });
38
+ // In a Form, the field takes part in its submit and reset.
39
+ $effect(() => form?.register({
40
+ validate() {
41
+ shown = true;
42
+ return problem === undefined;
43
+ },
44
+ resetValidation() {
45
+ shown = false;
46
+ dirty = false;
47
+ },
48
+ control: options.control
49
+ }));
50
+ return {
51
+ get error() {
52
+ if (typeof outside === 'string')
53
+ return outside;
54
+ return (shown && problem) || outside;
55
+ },
56
+ changed() {
57
+ dirty = true;
58
+ if (mode === 'input')
59
+ shown = true;
60
+ },
61
+ left() {
62
+ if (dirty && mode !== 'submit')
63
+ shown = true;
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,24 @@
1
+ import type { LocaleInstance } from '../locale/locale.js';
2
+ /**
3
+ * Reads what the user typed as a number written in `locale`: group separators and spaces are
4
+ * ignored, the locale's decimal separator and minus sign are understood. `null` when nothing was
5
+ * typed, `NaN` when the text is not a number.
6
+ *
7
+ * @example
8
+ * parseNumber('1,234.5', 'en-US'); // 1234.5
9
+ * parseNumber('1.234,5', 'de-DE'); // 1234.5
10
+ * parseNumber('', 'en-US'); // null
11
+ */
12
+ export declare function parseNumber(text: string, locale: string): number | null;
13
+ /** The HTML constraints of a number field. */
14
+ export interface NumberConstraints {
15
+ required?: boolean | null;
16
+ min?: number | null;
17
+ max?: number | null;
18
+ step?: number | null;
19
+ }
20
+ /**
21
+ * The message for the first constraint `value` breaks, worded by the locale, with numbers in its
22
+ * format. `NaN` stands for text that is not a number. As in HTML, steps count from `min`.
23
+ */
24
+ export declare function numberConstraintMessage(value: number | null, constraints: NumberConstraints, locale: LocaleInstance): string | undefined;
@@ -0,0 +1,71 @@
1
+ // The separators each locale writes numbers with, read once per locale from Intl. Keys are
2
+ // locale tags, not user data, so the cache is safe to share across server requests.
3
+ const symbols = new Map();
4
+ function symbolsOf(locale) {
5
+ let found = symbols.get(locale);
6
+ if (!found) {
7
+ const parts = new Intl.NumberFormat(locale).formatToParts(-12345.6);
8
+ const part = (type) => parts.find((item) => item.type === type)?.value;
9
+ found = {
10
+ group: part('group') ?? ',',
11
+ decimal: part('decimal') ?? '.',
12
+ minus: part('minusSign') ?? '-'
13
+ };
14
+ symbols.set(locale, found);
15
+ }
16
+ return found;
17
+ }
18
+ const SPACE = /[\s\u00a0\u202f]/;
19
+ const SPACES = /[\s\u00a0\u202f]/g;
20
+ const PLAIN = /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)$/;
21
+ /**
22
+ * Reads what the user typed as a number written in `locale`: group separators and spaces are
23
+ * ignored, the locale's decimal separator and minus sign are understood. `null` when nothing was
24
+ * typed, `NaN` when the text is not a number.
25
+ *
26
+ * @example
27
+ * parseNumber('1,234.5', 'en-US'); // 1234.5
28
+ * parseNumber('1.234,5', 'de-DE'); // 1234.5
29
+ * parseNumber('', 'en-US'); // null
30
+ */
31
+ export function parseNumber(text, locale) {
32
+ const { group, decimal, minus } = symbolsOf(locale);
33
+ let plain = text.trim();
34
+ if (plain === '')
35
+ return null;
36
+ if (!SPACE.test(group))
37
+ plain = plain.split(group).join('');
38
+ plain = plain
39
+ .replace(SPACES, '')
40
+ .split(minus)
41
+ .join('-')
42
+ .replace('\u2212', '-')
43
+ .split(decimal)
44
+ .join('.');
45
+ return PLAIN.test(plain) ? Number(plain) : Number.NaN;
46
+ }
47
+ /** Whether `value` is a whole number of steps from `base`, allowing for binary rounding. */
48
+ function onStep(value, step, base) {
49
+ const steps = (value - base) / step;
50
+ return Math.abs(steps - Math.round(steps)) < 1e-9;
51
+ }
52
+ /**
53
+ * The message for the first constraint `value` breaks, worded by the locale, with numbers in its
54
+ * format. `NaN` stands for text that is not a number. As in HTML, steps count from `min`.
55
+ */
56
+ export function numberConstraintMessage(value, constraints, locale) {
57
+ const { required, min, max, step } = constraints;
58
+ if (value === null)
59
+ return required ? locale.t('required') : undefined;
60
+ if (Number.isNaN(value))
61
+ return locale.t('badInput');
62
+ const format = (number) => locale.formatNumber(number, { maximumFractionDigits: 20 });
63
+ if (min != null && value < min)
64
+ return locale.t('rangeUnderflow', { min: format(min) });
65
+ if (max != null && value > max)
66
+ return locale.t('rangeOverflow', { max: format(max) });
67
+ if (step != null && step > 0 && !onStep(value, step, min ?? 0)) {
68
+ return locale.t('stepMismatch', { step: format(step) });
69
+ }
70
+ return undefined;
71
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A validation rule: `true` when the value is valid, otherwise the message to show. Rules run
3
+ * in order after the control's HTML constraints, and the first message wins. They are
4
+ * synchronous: a check that needs the server belongs in the submit handler, whose answer comes
5
+ * back through the field's `error`.
6
+ *
7
+ * @example
8
+ * const rules = [(value: string) => value.endsWith('.com') || 'Use a .com address'];
9
+ */
10
+ export type Rule<T> = (value: T) => true | string;
11
+ export declare const VALIDATE_ON: readonly ["blur", "input", "submit"];
12
+ /**
13
+ * When a field shows its message: `'blur'` once the user has changed the value and leaves the
14
+ * field, `'input'` from the first change, `'submit'` when the form is submitted. Once shown, the
15
+ * message follows every change, so fixing the value clears it at once.
16
+ */
17
+ export type ValidateOn = (typeof VALIDATE_ON)[number];
18
+ /**
19
+ * An error from outside the field, such as the server's answer: a message, a list of messages
20
+ * of which the first is shown, or `true` for an invalid state described elsewhere.
21
+ */
22
+ export type FieldError = string | readonly string[] | boolean | null;
@@ -0,0 +1 @@
1
+ export const VALIDATE_ON = ['blur', 'input', 'submit'];