@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.10.ff03a8d

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 (218) hide show
  1. package/.claude/skills/svelte-ui/SKILL.md +118 -0
  2. package/.claude/skills/themes/SKILL.md +303 -0
  3. package/CLAUDE.md +248 -0
  4. package/README.md +180 -42
  5. package/dist/components/Alert.svelte +119 -0
  6. package/dist/components/Alert.svelte.d.ts +29 -0
  7. package/dist/components/Badge/Badge.svelte +142 -69
  8. package/dist/components/Badge/Badge.svelte.d.ts +26 -6
  9. package/dist/components/Badge/index.d.ts +1 -1
  10. package/dist/components/Badge/index.js +1 -1
  11. package/dist/components/Banner.svelte +133 -0
  12. package/dist/components/Banner.svelte.d.ts +31 -0
  13. package/dist/components/Button.svelte +240 -0
  14. package/dist/components/Button.svelte.d.ts +33 -0
  15. package/dist/components/ButtonDropdown.svelte +145 -0
  16. package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
  17. package/dist/components/Calendar.svelte +259 -0
  18. package/dist/components/Calendar.svelte.d.ts +27 -0
  19. package/dist/components/Card.svelte +79 -0
  20. package/dist/components/Card.svelte.d.ts +17 -0
  21. package/dist/components/CardBody.svelte +41 -0
  22. package/dist/components/CardBody.svelte.d.ts +17 -0
  23. package/dist/components/CardFooter.svelte +51 -0
  24. package/dist/components/CardFooter.svelte.d.ts +16 -0
  25. package/dist/components/CardHeader.svelte +51 -0
  26. package/dist/components/CardHeader.svelte.d.ts +16 -0
  27. package/dist/components/Checkbox.svelte +131 -0
  28. package/dist/components/Checkbox.svelte.d.ts +16 -0
  29. package/dist/components/ConfirmDialog.svelte +103 -0
  30. package/dist/components/ConfirmDialog.svelte.d.ts +33 -0
  31. package/dist/components/DataTable.svelte +518 -0
  32. package/dist/components/DataTable.svelte.d.ts +74 -0
  33. package/dist/components/DatePicker.svelte +312 -0
  34. package/dist/components/DatePicker.svelte.d.ts +32 -0
  35. package/dist/components/DateTimeInput.svelte +93 -0
  36. package/dist/components/DateTimeInput.svelte.d.ts +20 -0
  37. package/dist/components/Dialog.svelte +151 -0
  38. package/dist/components/Dialog.svelte.d.ts +30 -0
  39. package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
  40. package/dist/components/EmailInput.svelte.d.ts +21 -0
  41. package/dist/components/Input.svelte +369 -0
  42. package/dist/components/Input.svelte.d.ts +35 -0
  43. package/dist/components/{List/List.svelte → List.svelte} +194 -133
  44. package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
  45. package/dist/components/Menu.svelte +117 -0
  46. package/dist/components/Menu.svelte.d.ts +20 -0
  47. package/dist/components/NotesEditor.svelte +127 -0
  48. package/dist/components/NotesEditor.svelte.d.ts +17 -0
  49. package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
  50. package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
  51. package/dist/components/PasswordInput.svelte +52 -0
  52. package/dist/components/PasswordInput.svelte.d.ts +20 -0
  53. package/dist/components/Popup.svelte +140 -0
  54. package/dist/components/Popup.svelte.d.ts +31 -0
  55. package/dist/components/Query.svelte +284 -0
  56. package/dist/components/Query.svelte.d.ts +15 -0
  57. package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
  58. package/dist/components/Rating.svelte.d.ts +13 -0
  59. package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
  60. package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
  61. package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
  62. package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
  63. package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
  64. package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
  65. package/dist/components/SideBarMenu/index.d.ts +2 -0
  66. package/dist/components/SideBarMenu/index.js +1 -0
  67. package/dist/components/TabStrip/TabStrip.svelte +384 -0
  68. package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
  69. package/dist/components/TabStrip/index.d.ts +3 -0
  70. package/dist/components/TabStrip/index.js +2 -0
  71. package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
  72. package/dist/components/TextArea.svelte.d.ts +38 -0
  73. package/dist/components/ThemeSelector.svelte +81 -0
  74. package/dist/components/ThemeSelector.svelte.d.ts +10 -0
  75. package/dist/components/TimePicker.svelte +148 -0
  76. package/dist/components/TimePicker.svelte.d.ts +28 -0
  77. package/dist/components/TimeRangeInput.svelte +203 -0
  78. package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
  79. package/dist/components/TimeSpinner.svelte +202 -0
  80. package/dist/components/TimeSpinner.svelte.d.ts +26 -0
  81. package/dist/components/Toast/Toaster.svelte +51 -0
  82. package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
  83. package/dist/components/Toast/toast.svelte.d.ts +29 -0
  84. package/dist/components/Toast/toast.svelte.js +27 -0
  85. package/dist/components/Toggle.svelte +93 -0
  86. package/dist/components/Toggle.svelte.d.ts +15 -0
  87. package/dist/data/dataset.d.ts +42 -0
  88. package/dist/data/dataset.js +3 -0
  89. package/dist/data/index.d.ts +3 -0
  90. package/dist/data/index.js +3 -0
  91. package/dist/data/query/ast.d.ts +62 -0
  92. package/dist/data/query/ast.js +12 -0
  93. package/dist/data/query/index.d.ts +3 -0
  94. package/dist/data/query/index.js +3 -0
  95. package/dist/data/query/lexer.d.ts +33 -0
  96. package/dist/data/query/lexer.js +225 -0
  97. package/dist/data/query/parser.d.ts +11 -0
  98. package/dist/data/query/parser.js +252 -0
  99. package/dist/data/table/grid.svelte.d.ts +57 -0
  100. package/dist/data/table/grid.svelte.js +139 -0
  101. package/dist/data/table/index.d.ts +2 -0
  102. package/dist/data/table/index.js +2 -0
  103. package/dist/data/table/types.d.ts +79 -0
  104. package/dist/index.d.ts +49 -22
  105. package/dist/index.js +46 -21
  106. package/dist/positioning/anchored.d.ts +61 -0
  107. package/dist/positioning/anchored.js +122 -0
  108. package/dist/positioning/tooltip.d.ts +41 -0
  109. package/dist/positioning/tooltip.js +147 -0
  110. package/dist/theme.css +205 -0
  111. package/dist/types/breakpoints.d.ts +24 -0
  112. package/dist/types/breakpoints.js +13 -0
  113. package/dist/types/responsive.d.ts +32 -0
  114. package/dist/types/responsive.js +54 -0
  115. package/dist/types/sizes.d.ts +10 -3
  116. package/dist/types/time.d.ts +19 -0
  117. package/dist/types/time.js +10 -0
  118. package/dist/types/variants.d.ts +8 -1
  119. package/dist/types/variants.js +16 -1
  120. package/docs/components.md +255 -0
  121. package/docs/examples.md +323 -0
  122. package/docs/paradigm.md +240 -0
  123. package/docs/themes.md +170 -0
  124. package/docs/usage.md +450 -0
  125. package/package.json +97 -63
  126. package/dist/components/Button/Button.svelte +0 -172
  127. package/dist/components/Button/Button.svelte.d.ts +0 -32
  128. package/dist/components/Button/index.d.ts +0 -1
  129. package/dist/components/Button/index.js +0 -1
  130. package/dist/components/Dialog/Dialog.svelte +0 -101
  131. package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
  132. package/dist/components/Dialog/index.d.ts +0 -1
  133. package/dist/components/Dialog/index.js +0 -1
  134. package/dist/components/ElementManager/ElementManager.svelte +0 -397
  135. package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
  136. package/dist/components/ElementManager/index.d.ts +0 -1
  137. package/dist/components/ElementManager/index.js +0 -1
  138. package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
  139. package/dist/components/EmailInput/index.d.ts +0 -1
  140. package/dist/components/EmailInput/index.js +0 -1
  141. package/dist/components/Icon/Icon.svelte +0 -74
  142. package/dist/components/Icon/Icon.svelte.d.ts +0 -13
  143. package/dist/components/Icon/index.d.ts +0 -1
  144. package/dist/components/Icon/index.js +0 -1
  145. package/dist/components/Input/Input.svelte +0 -268
  146. package/dist/components/Input/Input.svelte.d.ts +0 -18
  147. package/dist/components/Input/index.d.ts +0 -1
  148. package/dist/components/Input/index.js +0 -1
  149. package/dist/components/List/index.d.ts +0 -1
  150. package/dist/components/List/index.js +0 -1
  151. package/dist/components/ListItem/ListItem.svelte +0 -175
  152. package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
  153. package/dist/components/ListItem/index.d.ts +0 -2
  154. package/dist/components/ListItem/index.js +0 -2
  155. package/dist/components/ListView/ListView.svelte +0 -463
  156. package/dist/components/ListView/ListView.svelte.d.ts +0 -37
  157. package/dist/components/ListView/index.d.ts +0 -2
  158. package/dist/components/ListView/index.js +0 -2
  159. package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
  160. package/dist/components/NodeGraph/BaseNode.js +0 -30
  161. package/dist/components/NodeGraph/Edge.svelte +0 -60
  162. package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
  163. package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
  164. package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
  165. package/dist/components/NodeGraph/Node.svelte +0 -100
  166. package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
  167. package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
  168. package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
  169. package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
  170. package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
  171. package/dist/components/NodeGraph/NodePort.svelte +0 -75
  172. package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
  173. package/dist/components/NodeGraph/decorators.d.ts +0 -81
  174. package/dist/components/NodeGraph/decorators.js +0 -148
  175. package/dist/components/NodeGraph/index.d.ts +0 -9
  176. package/dist/components/NodeGraph/index.js +0 -9
  177. package/dist/components/NodeGraph/types.d.ts +0 -94
  178. package/dist/components/NodeGraph/types.js +0 -33
  179. package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
  180. package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
  181. package/dist/components/NotesEditor/index.d.ts +0 -1
  182. package/dist/components/NotesEditor/index.js +0 -1
  183. package/dist/components/NumberInput/index.d.ts +0 -1
  184. package/dist/components/NumberInput/index.js +0 -1
  185. package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
  186. package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
  187. package/dist/components/PasswordInput/index.d.ts +0 -1
  188. package/dist/components/PasswordInput/index.js +0 -1
  189. package/dist/components/Popup/index.d.ts +0 -1
  190. package/dist/components/Popup/index.js +0 -1
  191. package/dist/components/Rating/Rating.svelte.d.ts +0 -7
  192. package/dist/components/Rating/index.d.ts +0 -1
  193. package/dist/components/Rating/index.js +0 -1
  194. package/dist/components/ScrollView/ScrollView.svelte +0 -285
  195. package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
  196. package/dist/components/ScrollView/index.d.ts +0 -1
  197. package/dist/components/ScrollView/index.js +0 -1
  198. package/dist/components/SearchInput/index.d.ts +0 -1
  199. package/dist/components/SearchInput/index.js +0 -1
  200. package/dist/components/Select/index.d.ts +0 -1
  201. package/dist/components/Select/index.js +0 -1
  202. package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
  203. package/dist/components/TextArea/index.d.ts +0 -1
  204. package/dist/components/TextArea/index.js +0 -1
  205. package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
  206. package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
  207. package/dist/components/ThemeSelector/index.d.ts +0 -1
  208. package/dist/components/ThemeSelector/index.js +0 -1
  209. package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
  210. package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
  211. package/dist/components/TooltipHandler/index.d.ts +0 -1
  212. package/dist/components/TooltipHandler/index.js +0 -1
  213. package/dist/styles/sizes.d.ts +0 -78
  214. package/dist/styles/sizes.js +0 -120
  215. package/dist/styles/variants.d.ts +0 -106
  216. package/dist/styles/variants.js +0 -194
  217. package/dist/types/ManagerTypes.d.ts +0 -42
  218. /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
@@ -1,8 +1,11 @@
1
1
  <script lang="ts">
2
- import Input from '../Input/Input.svelte';
3
- import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import Input from "./Input.svelte";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
4
+ import type { Size } from "../types/sizes.js";
5
+ import type { Responsive } from "../types/responsive.js";
6
+ import type { Variant } from "../types/variants.js";
4
7
 
5
- interface Props extends Omit<HTMLInputAttributes, 'type' | 'value'> {
8
+ interface Props extends Omit<HTMLInputAttributes, "type" | "value" | "size"> {
6
9
  value: number | null;
7
10
  class?: string;
8
11
  inputClass?: string;
@@ -10,6 +13,10 @@
10
13
  valid?: boolean;
11
14
  touched?: boolean;
12
15
  element?: HTMLInputElement;
16
+ size?: Responsive<Size>;
17
+ /** Shared axes — forwarded to the underlying Input. */
18
+ variant?: Variant;
19
+ borderless?: boolean;
13
20
  min?: number;
14
21
  max?: number;
15
22
  step?: number;
@@ -21,8 +28,8 @@
21
28
  currency?: string; // e.g., '$', '€', '£', '¥', etc.
22
29
  decimalSeparator?: string; // Decimal separator (default: '.')
23
30
  thousandsChar?: string; // Thousands separator character (default: ',')
24
- roundingMode?: 'round' | 'floor' | 'ceil' | 'trunc'; // Rounding behavior for integers
25
- negativeMode?: 'minus' | 'parentheses' | 'both'; // How to display/accept negative numbers
31
+ roundingMode?: "round" | "floor" | "ceil" | "trunc"; // Rounding behavior for integers
32
+ negativeMode?: "minus" | "parentheses" | "both"; // How to display/accept negative numbers
26
33
  }
27
34
 
28
35
  let {
@@ -35,30 +42,36 @@
35
42
  max,
36
43
  step = 1,
37
44
  allowDecimals = true,
38
- decimalPlaces = 2,
45
+ // Integer by default. Currency / accounting callers already
46
+ // pass `decimalPlaces={2}` explicitly (see the showcase), so
47
+ // this default only affects bare `<NumberInput bind:value=… />`
48
+ // which reads as an integer field 99% of the time (port numbers,
49
+ // intervals, counts). Prevents the `22 → 22.00` surprise where
50
+ // callers had to opt OUT of decimals to display a whole number.
51
+ decimalPlaces = 0,
39
52
  thousandsSeparator: enableThousandsSeparator = false,
40
- prefix = '',
41
- suffix = '',
53
+ prefix = "",
54
+ suffix = "",
42
55
  currency,
43
- decimalSeparator = '.',
44
- thousandsChar = ',',
45
- roundingMode = 'round',
46
- negativeMode = 'minus',
47
- placeholder = 'Enter a number...',
56
+ decimalSeparator = ".",
57
+ thousandsChar = ",",
58
+ roundingMode = "round",
59
+ negativeMode = "minus",
60
+ placeholder = "Enter a number...",
48
61
  ...restProps
49
62
  }: Props = $props();
50
63
 
51
- let displayValue = $state('');
64
+ let displayValue = $state("");
52
65
  let focused = $state(false);
53
66
 
54
67
  // Convert number to formatted display string
55
68
  function formatNumber(num: number | null): string {
56
- if (num === null || num === undefined || isNaN(num)) return '';
57
-
69
+ if (num === null || num === undefined || isNaN(num)) return "";
70
+
58
71
  const isNegative = num < 0;
59
72
  const absoluteNum = Math.abs(num);
60
73
  let formatted = absoluteNum.toString();
61
-
74
+
62
75
  // Handle decimal places and rounding
63
76
  if (allowDecimals && decimalPlaces > 0) {
64
77
  formatted = absoluteNum.toFixed(decimalPlaces);
@@ -66,13 +79,13 @@
66
79
  // Apply rounding mode for integers
67
80
  let rounded: number;
68
81
  switch (roundingMode) {
69
- case 'floor':
82
+ case "floor":
70
83
  rounded = Math.floor(absoluteNum);
71
84
  break;
72
- case 'ceil':
85
+ case "ceil":
73
86
  rounded = Math.ceil(absoluteNum);
74
87
  break;
75
- case 'trunc':
88
+ case "trunc":
76
89
  rounded = Math.trunc(absoluteNum);
77
90
  break;
78
91
  default: // 'round'
@@ -80,22 +93,22 @@
80
93
  }
81
94
  formatted = rounded.toString();
82
95
  }
83
-
96
+
84
97
  // Add thousands separator and convert to custom format
85
98
  if (enableThousandsSeparator) {
86
- const parts = formatted.split('.');
99
+ const parts = formatted.split(".");
87
100
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsChar);
88
101
  formatted = parts.join(decimalSeparator);
89
- } else if (decimalSeparator !== '.') {
102
+ } else if (decimalSeparator !== ".") {
90
103
  // Even without thousands separator, we need to convert decimal separator
91
- formatted = formatted.replace('.', decimalSeparator);
104
+ formatted = formatted.replace(".", decimalSeparator);
92
105
  }
93
-
106
+
94
107
  const finalPrefix = currency || prefix;
95
-
108
+
96
109
  // Handle negative number display
97
110
  if (isNegative) {
98
- if (negativeMode === 'parentheses') {
111
+ if (negativeMode === "parentheses") {
99
112
  // For parentheses, put everything (prefix, number, suffix) inside
100
113
  return `(${finalPrefix}${formatted}${suffix})`;
101
114
  } else {
@@ -103,56 +116,62 @@
103
116
  return `-${finalPrefix}${formatted}${suffix}`;
104
117
  }
105
118
  }
106
-
119
+
107
120
  return `${finalPrefix}${formatted}${suffix}`;
108
121
  }
109
122
 
110
123
  // Parse display string back to number
111
124
  function parseNumber(str: string): number | null {
112
- if (!str || str.trim() === '') return null;
113
-
125
+ if (!str || str.trim() === "") return null;
126
+
114
127
  // Check for negative indicators
115
- const hasParentheses = str.includes('(') && str.includes(')');
116
- const hasMinus = str.includes('-');
128
+ const hasParentheses = str.includes("(") && str.includes(")");
129
+ const hasMinus = str.includes("-");
117
130
  const isNegative = hasParentheses || hasMinus;
118
-
131
+
119
132
  // Remove prefix, suffix, currency
120
133
  const finalPrefix = currency || prefix;
121
134
  let cleaned = str;
122
-
135
+
123
136
  if (finalPrefix) {
124
- const escapedPrefix = finalPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
137
+ const escapedPrefix = finalPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
125
138
  // Remove prefix from beginning, or after negative indicators
126
- cleaned = cleaned.replace(new RegExp(`^${escapedPrefix}`), ''); // $123
127
- cleaned = cleaned.replace(new RegExp(`^-${escapedPrefix}`), '-'); // -$123 -> -123
128
- cleaned = cleaned.replace(new RegExp(`^\\(${escapedPrefix}`), '('); // ($123 -> (123
139
+ cleaned = cleaned.replace(new RegExp(`^${escapedPrefix}`), ""); // $123
140
+ cleaned = cleaned.replace(new RegExp(`^-${escapedPrefix}`), "-"); // -$123 -> -123
141
+ cleaned = cleaned.replace(new RegExp(`^\\(${escapedPrefix}`), "("); // ($123 -> (123
129
142
  }
130
-
143
+
131
144
  if (suffix) {
132
- const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
133
- cleaned = cleaned.replace(new RegExp(`${escapedSuffix}$`), '');
145
+ const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
146
+ cleaned = cleaned.replace(new RegExp(`${escapedSuffix}$`), "");
134
147
  }
135
-
148
+
136
149
  // Remove negative indicators
137
- cleaned = cleaned.replace(/[()]/g, ''); // Remove parentheses
138
- cleaned = cleaned.replace(/-/g, ''); // Remove minus signs
139
-
150
+ cleaned = cleaned.replace(/[()]/g, ""); // Remove parentheses
151
+ cleaned = cleaned.replace(/-/g, ""); // Remove minus signs
152
+
140
153
  // Remove thousands separators (both , and . could be thousands separators)
141
- cleaned = cleaned.replace(new RegExp(`\\${thousandsChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g'), '');
142
-
154
+ cleaned = cleaned.replace(
155
+ new RegExp(`\\${thousandsChar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g"),
156
+ "",
157
+ );
158
+
143
159
  // Convert decimal separator to standard decimal point
144
- if (decimalSeparator !== '.') {
160
+ if (decimalSeparator !== ".") {
145
161
  // Replace the decimal separator with standard decimal point
146
162
  // But only the last occurrence to handle cases like "1.234,56"
147
163
  const lastSeparatorIndex = cleaned.lastIndexOf(decimalSeparator);
148
164
  if (lastSeparatorIndex !== -1) {
149
- cleaned = cleaned.substring(0, lastSeparatorIndex) + '.' + cleaned.substring(lastSeparatorIndex + 1);
165
+ cleaned =
166
+ cleaned.substring(0, lastSeparatorIndex) +
167
+ "." +
168
+ cleaned.substring(lastSeparatorIndex + 1);
150
169
  }
151
170
  }
152
-
171
+
153
172
  const parsed = parseFloat(cleaned);
154
173
  if (isNaN(parsed)) return null;
155
-
174
+
156
175
  return isNegative ? -parsed : parsed;
157
176
  }
158
177
 
@@ -166,12 +185,12 @@
166
185
  // Default validation
167
186
  const defaultValidator = (val: number | null) => {
168
187
  if (val === null) return true; // Allow null/empty values
169
-
188
+
170
189
  if (min !== undefined && val < min) return `Value must be at least ${min}`;
171
190
  if (max !== undefined && val > max) return `Value must be at most ${max}`;
172
-
173
- if (!allowDecimals && val % 1 !== 0) return 'Decimal values are not allowed';
174
-
191
+
192
+ if (!allowDecimals && val % 1 !== 0) return "Decimal values are not allowed";
193
+
175
194
  return true;
176
195
  };
177
196
 
@@ -180,49 +199,49 @@
180
199
 
181
200
  function handleInput(inputValue: string) {
182
201
  displayValue = inputValue;
183
-
202
+
184
203
  // Only skip parsing for truly incomplete inputs that have no numeric content
185
204
  const finalPrefix = currency || prefix;
186
-
205
+
187
206
  // Check if input is incomplete (has negative indicators but no actual numbers)
188
207
  const incompletePatterns = [
189
- '-', // Just minus
190
- '(', // Just opening paren
191
- '()', // Empty parens
208
+ "-", // Just minus
209
+ "(", // Just opening paren
210
+ "()", // Empty parens
192
211
  ];
193
-
212
+
194
213
  // Add prefix-related incomplete patterns
195
214
  if (finalPrefix) {
196
215
  incompletePatterns.push(
197
- `-${finalPrefix}`, // -$, -€, etc.
198
- `(${finalPrefix}`, // ($, (€, etc.
199
- `(${finalPrefix})` // ($), (€), etc.
216
+ `-${finalPrefix}`, // -$, -€, etc.
217
+ `(${finalPrefix}`, // ($, (€, etc.
218
+ `(${finalPrefix})`, // ($), (€), etc.
200
219
  );
201
220
  }
202
-
203
- // Add suffix-related incomplete patterns
221
+
222
+ // Add suffix-related incomplete patterns
204
223
  if (suffix) {
205
224
  incompletePatterns.push(
206
- `-${suffix}`, // -%, etc.
207
- `(${suffix}`, // (%, etc.
208
- `(${suffix})` // (%), etc.
225
+ `-${suffix}`, // -%, etc.
226
+ `(${suffix}`, // (%, etc.
227
+ `(${suffix})`, // (%), etc.
209
228
  );
210
-
229
+
211
230
  // Combined prefix and suffix patterns
212
231
  if (finalPrefix) {
213
232
  incompletePatterns.push(
214
- `-${finalPrefix}${suffix}`, // -$%, etc.
215
- `(${finalPrefix}${suffix}`, // ($%, etc.
216
- `(${finalPrefix}${suffix})` // ($%), etc.
233
+ `-${finalPrefix}${suffix}`, // -$%, etc.
234
+ `(${finalPrefix}${suffix}`, // ($%, etc.
235
+ `(${finalPrefix}${suffix})`, // ($%), etc.
217
236
  );
218
237
  }
219
238
  }
220
-
239
+
221
240
  if (incompletePatterns.includes(inputValue)) {
222
241
  // Keep the current value but update display for incomplete inputs
223
242
  return;
224
243
  }
225
-
244
+
226
245
  const parsed = parseNumber(inputValue);
227
246
  value = parsed;
228
247
  }
@@ -244,15 +263,16 @@
244
263
  function handleKeyDown(event: KeyboardEvent) {
245
264
  // Allow control keys (backspace, delete, arrow keys, tab, etc.)
246
265
  if (
247
- event.key === 'Backspace' ||
248
- event.key === 'Delete' ||
249
- event.key === 'Tab' ||
250
- event.key === 'Escape' ||
251
- event.key === 'Enter' ||
252
- event.key.startsWith('Arrow') ||
253
- event.key === 'Home' ||
254
- event.key === 'End' ||
255
- (event.ctrlKey || event.metaKey) // Allow Ctrl/Cmd shortcuts
266
+ event.key === "Backspace" ||
267
+ event.key === "Delete" ||
268
+ event.key === "Tab" ||
269
+ event.key === "Escape" ||
270
+ event.key === "Enter" ||
271
+ event.key.startsWith("Arrow") ||
272
+ event.key === "Home" ||
273
+ event.key === "End" ||
274
+ event.ctrlKey ||
275
+ event.metaKey // Allow Ctrl/Cmd shortcuts
256
276
  ) {
257
277
  return;
258
278
  }
@@ -261,18 +281,18 @@
261
281
  const finalPrefix = currency || prefix;
262
282
  const input = event.target as HTMLInputElement;
263
283
  const cursorPosition = input.selectionStart || 0;
264
-
284
+
265
285
  // Basic allowed characters
266
- const allowedChars = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
267
-
286
+ const allowedChars = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
287
+
268
288
  // Always allow both comma and period for international input flexibility
269
- allowedChars.add(',');
270
- allowedChars.add('.');
271
-
289
+ allowedChars.add(",");
290
+ allowedChars.add(".");
291
+
272
292
  // Add currency characters (but validate position)
273
293
  if (finalPrefix && event.key === finalPrefix) {
274
294
  // Only allow currency at the beginning (after minus if present)
275
- const hasMinusAtStart = currentValue.startsWith('-');
295
+ const hasMinusAtStart = currentValue.startsWith("-");
276
296
  const expectedPosition = hasMinusAtStart ? 1 : 0;
277
297
  if (cursorPosition === expectedPosition && !currentValue.includes(finalPrefix)) {
278
298
  allowedChars.add(finalPrefix);
@@ -281,7 +301,7 @@
281
301
  return;
282
302
  }
283
303
  }
284
-
304
+
285
305
  // Add suffix characters (but validate position and prevent duplicates)
286
306
  if (suffix && event.key === suffix) {
287
307
  // Only allow suffix at the end and if not already present
@@ -292,31 +312,35 @@
292
312
  return;
293
313
  }
294
314
  }
295
-
315
+
296
316
  // Always allow both minus and parentheses for input (negativeMode only affects display formatting)
297
- if (event.key === '-') {
317
+ if (event.key === "-") {
298
318
  // Only allow minus at the very beginning and if not already present
299
- if (cursorPosition === 0 && !currentValue.includes('-')) {
300
- allowedChars.add('-');
319
+ if (cursorPosition === 0 && !currentValue.includes("-")) {
320
+ allowedChars.add("-");
301
321
  } else {
302
322
  event.preventDefault();
303
323
  return;
304
324
  }
305
325
  }
306
-
307
- if (event.key === '(') {
326
+
327
+ if (event.key === "(") {
308
328
  // Only allow opening parenthesis at the beginning and if not already present
309
- if (cursorPosition === 0 && !currentValue.includes('(')) {
310
- allowedChars.add('(');
329
+ if (cursorPosition === 0 && !currentValue.includes("(")) {
330
+ allowedChars.add("(");
311
331
  } else {
312
332
  event.preventDefault();
313
333
  return;
314
334
  }
315
335
  }
316
- if (event.key === ')') {
336
+ if (event.key === ")") {
317
337
  // Only allow closing parenthesis at the end, if opening exists, and no closing yet
318
- if (cursorPosition === currentValue.length && currentValue.includes('(') && !currentValue.includes(')')) {
319
- allowedChars.add(')');
338
+ if (
339
+ cursorPosition === currentValue.length &&
340
+ currentValue.includes("(") &&
341
+ !currentValue.includes(")")
342
+ ) {
343
+ allowedChars.add(")");
320
344
  } else {
321
345
  event.preventDefault();
322
346
  return;
@@ -331,6 +355,23 @@
331
355
 
332
356
  // Convert our number value to string for the Input component
333
357
  let stringValue = $derived(displayValue);
358
+
359
+ // Use native <input type="number"> when the caller isn't using any
360
+ // of the custom-formatting features (prefix / suffix / currency /
361
+ // thousands / non-`.` decimal / paren-negative). That gives them
362
+ // the browser spinner + arrow-key increment + guaranteed mobile
363
+ // numeric keypad. When formatting IS used, stay on type="text" so
364
+ // the browser doesn't strip `$`, `%`, `,`, `(`, etc.
365
+ const inputType = $derived(
366
+ prefix ||
367
+ suffix ||
368
+ currency ||
369
+ enableThousandsSeparator ||
370
+ decimalSeparator !== "." ||
371
+ negativeMode === "parentheses"
372
+ ? "text"
373
+ : "number",
374
+ );
334
375
  </script>
335
376
 
336
377
  <Input
@@ -338,7 +379,7 @@
338
379
  bind:valid
339
380
  bind:touched
340
381
  bind:element
341
- type="text"
382
+ type={inputType}
342
383
  inputmode="numeric"
343
384
  validate={(val) => {
344
385
  const parsed = parseNumber(val);
@@ -350,4 +391,4 @@
350
391
  onkeydown={handleKeyDown}
351
392
  {placeholder}
352
393
  {...restProps}
353
- />
394
+ />
@@ -1,5 +1,8 @@
1
- import type { HTMLInputAttributes } from 'svelte/elements';
2
- interface Props extends Omit<HTMLInputAttributes, 'type' | 'value'> {
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ import type { Size } from "../types/sizes.js";
3
+ import type { Responsive } from "../types/responsive.js";
4
+ import type { Variant } from "../types/variants.js";
5
+ interface Props extends Omit<HTMLInputAttributes, "type" | "value" | "size"> {
3
6
  value: number | null;
4
7
  class?: string;
5
8
  inputClass?: string;
@@ -7,6 +10,10 @@ interface Props extends Omit<HTMLInputAttributes, 'type' | 'value'> {
7
10
  valid?: boolean;
8
11
  touched?: boolean;
9
12
  element?: HTMLInputElement;
13
+ size?: Responsive<Size>;
14
+ /** Shared axes — forwarded to the underlying Input. */
15
+ variant?: Variant;
16
+ borderless?: boolean;
10
17
  min?: number;
11
18
  max?: number;
12
19
  step?: number;
@@ -18,8 +25,8 @@ interface Props extends Omit<HTMLInputAttributes, 'type' | 'value'> {
18
25
  currency?: string;
19
26
  decimalSeparator?: string;
20
27
  thousandsChar?: string;
21
- roundingMode?: 'round' | 'floor' | 'ceil' | 'trunc';
22
- negativeMode?: 'minus' | 'parentheses' | 'both';
28
+ roundingMode?: "round" | "floor" | "ceil" | "trunc";
29
+ negativeMode?: "minus" | "parentheses" | "both";
23
30
  }
24
31
  declare const NumberInput: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
25
32
  type NumberInput = ReturnType<typeof NumberInput>;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import Input from "./Input.svelte";
3
+ import Eye from "~icons/mdi/eye";
4
+ import EyeOff from "~icons/mdi/eye-off";
5
+ import type { HTMLInputAttributes } from "svelte/elements";
6
+ import type { Size } from "../types/sizes.js";
7
+ import type { Responsive } from "../types/responsive.js";
8
+ import type { Variant } from "../types/variants.js";
9
+
10
+ interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
11
+ value: string;
12
+ class?: string;
13
+ inputClass?: string;
14
+ validate?: (value: string) => boolean | string;
15
+ valid?: boolean;
16
+ touched?: boolean;
17
+ element?: HTMLInputElement;
18
+ size?: Responsive<Size>;
19
+ /** Shared axes — forwarded to the underlying Input. */
20
+ variant?: Variant;
21
+ borderless?: boolean;
22
+ }
23
+
24
+ let {
25
+ value = $bindable(),
26
+ valid = $bindable(true),
27
+ touched = $bindable(false),
28
+ element = $bindable(),
29
+ ...restProps
30
+ }: Props = $props();
31
+
32
+ let showPassword = $state(false);
33
+
34
+ function toggleVisibility() {
35
+ showPassword = !showPassword;
36
+ }
37
+ </script>
38
+
39
+ <Input
40
+ bind:value
41
+ bind:valid
42
+ bind:touched
43
+ bind:element
44
+ type={showPassword ? "text" : "password"}
45
+ iconClickable={true}
46
+ onIconClick={toggleVisibility}
47
+ {...restProps}
48
+ >
49
+ {#snippet icon()}
50
+ {#if showPassword}<EyeOff />{:else}<Eye />{/if}
51
+ {/snippet}
52
+ </Input>
@@ -0,0 +1,20 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ import type { Size } from "../types/sizes.js";
3
+ import type { Responsive } from "../types/responsive.js";
4
+ import type { Variant } from "../types/variants.js";
5
+ interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
6
+ value: string;
7
+ class?: string;
8
+ inputClass?: string;
9
+ validate?: (value: string) => boolean | string;
10
+ valid?: boolean;
11
+ touched?: boolean;
12
+ element?: HTMLInputElement;
13
+ size?: Responsive<Size>;
14
+ /** Shared axes — forwarded to the underlying Input. */
15
+ variant?: Variant;
16
+ borderless?: boolean;
17
+ }
18
+ declare const PasswordInput: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
19
+ type PasswordInput = ReturnType<typeof PasswordInput>;
20
+ export default PasswordInput;