@spaethtech/svelte-ui 0.1.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +166 -42
  2. package/dist/components/Alert.svelte +119 -0
  3. package/dist/components/Alert.svelte.d.ts +29 -0
  4. package/dist/components/Badge/Badge.svelte +142 -69
  5. package/dist/components/Badge/Badge.svelte.d.ts +26 -6
  6. package/dist/components/Badge/index.d.ts +1 -1
  7. package/dist/components/Badge/index.js +1 -1
  8. package/dist/components/Banner.svelte +133 -0
  9. package/dist/components/Banner.svelte.d.ts +31 -0
  10. package/dist/components/Button.svelte +240 -0
  11. package/dist/components/Button.svelte.d.ts +33 -0
  12. package/dist/components/ButtonDropdown.svelte +145 -0
  13. package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
  14. package/dist/components/Calendar.svelte +259 -0
  15. package/dist/components/Calendar.svelte.d.ts +27 -0
  16. package/dist/components/Card.svelte +78 -0
  17. package/dist/components/Card.svelte.d.ts +17 -0
  18. package/dist/components/CardBody.svelte +41 -0
  19. package/dist/components/CardBody.svelte.d.ts +17 -0
  20. package/dist/components/CardFooter.svelte +49 -0
  21. package/dist/components/CardFooter.svelte.d.ts +16 -0
  22. package/dist/components/CardHeader.svelte +49 -0
  23. package/dist/components/CardHeader.svelte.d.ts +16 -0
  24. package/dist/components/Checkbox.svelte +131 -0
  25. package/dist/components/Checkbox.svelte.d.ts +16 -0
  26. package/dist/components/DataTable.svelte +518 -0
  27. package/dist/components/DataTable.svelte.d.ts +74 -0
  28. package/dist/components/DatePicker.svelte +312 -0
  29. package/dist/components/DatePicker.svelte.d.ts +32 -0
  30. package/dist/components/DateTimeInput.svelte +93 -0
  31. package/dist/components/DateTimeInput.svelte.d.ts +20 -0
  32. package/dist/components/Dialog.svelte +132 -0
  33. package/dist/components/Dialog.svelte.d.ts +24 -0
  34. package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
  35. package/dist/components/EmailInput.svelte.d.ts +21 -0
  36. package/dist/components/Input.svelte +369 -0
  37. package/dist/components/Input.svelte.d.ts +35 -0
  38. package/dist/components/{List/List.svelte → List.svelte} +194 -133
  39. package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
  40. package/dist/components/Menu.svelte +117 -0
  41. package/dist/components/Menu.svelte.d.ts +20 -0
  42. package/dist/components/NotesEditor.svelte +127 -0
  43. package/dist/components/NotesEditor.svelte.d.ts +17 -0
  44. package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
  45. package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
  46. package/dist/components/PasswordInput.svelte +52 -0
  47. package/dist/components/PasswordInput.svelte.d.ts +20 -0
  48. package/dist/components/Popup.svelte +140 -0
  49. package/dist/components/Popup.svelte.d.ts +31 -0
  50. package/dist/components/Query.svelte +284 -0
  51. package/dist/components/Query.svelte.d.ts +15 -0
  52. package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
  53. package/dist/components/Rating.svelte.d.ts +13 -0
  54. package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
  55. package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
  56. package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
  57. package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
  58. package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
  59. package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
  60. package/dist/components/SideBarMenu/index.d.ts +2 -0
  61. package/dist/components/SideBarMenu/index.js +1 -0
  62. package/dist/components/TabStrip/TabStrip.svelte +384 -0
  63. package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
  64. package/dist/components/TabStrip/index.d.ts +3 -0
  65. package/dist/components/TabStrip/index.js +2 -0
  66. package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
  67. package/dist/components/TextArea.svelte.d.ts +38 -0
  68. package/dist/components/ThemeSelector.svelte +81 -0
  69. package/dist/components/ThemeSelector.svelte.d.ts +10 -0
  70. package/dist/components/TimePicker.svelte +148 -0
  71. package/dist/components/TimePicker.svelte.d.ts +28 -0
  72. package/dist/components/TimeRangeInput.svelte +203 -0
  73. package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
  74. package/dist/components/TimeSpinner.svelte +202 -0
  75. package/dist/components/TimeSpinner.svelte.d.ts +26 -0
  76. package/dist/components/Toast/Toaster.svelte +51 -0
  77. package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
  78. package/dist/components/Toast/toast.svelte.d.ts +29 -0
  79. package/dist/components/Toast/toast.svelte.js +27 -0
  80. package/dist/components/Toggle.svelte +93 -0
  81. package/dist/components/Toggle.svelte.d.ts +15 -0
  82. package/dist/data/dataset.d.ts +42 -0
  83. package/dist/data/dataset.js +3 -0
  84. package/dist/data/index.d.ts +3 -0
  85. package/dist/data/index.js +3 -0
  86. package/dist/data/query/ast.d.ts +62 -0
  87. package/dist/data/query/ast.js +12 -0
  88. package/dist/data/query/index.d.ts +3 -0
  89. package/dist/data/query/index.js +3 -0
  90. package/dist/data/query/lexer.d.ts +33 -0
  91. package/dist/data/query/lexer.js +225 -0
  92. package/dist/data/query/parser.d.ts +11 -0
  93. package/dist/data/query/parser.js +252 -0
  94. package/dist/data/table/grid.svelte.d.ts +57 -0
  95. package/dist/data/table/grid.svelte.js +139 -0
  96. package/dist/data/table/index.d.ts +2 -0
  97. package/dist/data/table/index.js +2 -0
  98. package/dist/data/table/types.d.ts +79 -0
  99. package/dist/index.d.ts +48 -22
  100. package/dist/index.js +45 -21
  101. package/dist/positioning/anchored.d.ts +61 -0
  102. package/dist/positioning/anchored.js +122 -0
  103. package/dist/positioning/tooltip.d.ts +41 -0
  104. package/dist/positioning/tooltip.js +147 -0
  105. package/dist/theme.css +205 -0
  106. package/dist/types/breakpoints.d.ts +24 -0
  107. package/dist/types/breakpoints.js +13 -0
  108. package/dist/types/responsive.d.ts +32 -0
  109. package/dist/types/responsive.js +54 -0
  110. package/dist/types/sizes.d.ts +10 -3
  111. package/dist/types/time.d.ts +19 -0
  112. package/dist/types/time.js +10 -0
  113. package/dist/types/variants.d.ts +8 -1
  114. package/dist/types/variants.js +16 -1
  115. package/package.json +93 -63
  116. package/dist/components/Button/Button.svelte +0 -172
  117. package/dist/components/Button/Button.svelte.d.ts +0 -32
  118. package/dist/components/Button/index.d.ts +0 -1
  119. package/dist/components/Button/index.js +0 -1
  120. package/dist/components/Dialog/Dialog.svelte +0 -101
  121. package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
  122. package/dist/components/Dialog/index.d.ts +0 -1
  123. package/dist/components/Dialog/index.js +0 -1
  124. package/dist/components/ElementManager/ElementManager.svelte +0 -397
  125. package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
  126. package/dist/components/ElementManager/index.d.ts +0 -1
  127. package/dist/components/ElementManager/index.js +0 -1
  128. package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
  129. package/dist/components/EmailInput/index.d.ts +0 -1
  130. package/dist/components/EmailInput/index.js +0 -1
  131. package/dist/components/Icon/Icon.svelte +0 -74
  132. package/dist/components/Icon/Icon.svelte.d.ts +0 -13
  133. package/dist/components/Icon/index.d.ts +0 -1
  134. package/dist/components/Icon/index.js +0 -1
  135. package/dist/components/Input/Input.svelte +0 -268
  136. package/dist/components/Input/Input.svelte.d.ts +0 -18
  137. package/dist/components/Input/index.d.ts +0 -1
  138. package/dist/components/Input/index.js +0 -1
  139. package/dist/components/List/index.d.ts +0 -1
  140. package/dist/components/List/index.js +0 -1
  141. package/dist/components/ListItem/ListItem.svelte +0 -175
  142. package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
  143. package/dist/components/ListItem/index.d.ts +0 -2
  144. package/dist/components/ListItem/index.js +0 -2
  145. package/dist/components/ListView/ListView.svelte +0 -463
  146. package/dist/components/ListView/ListView.svelte.d.ts +0 -37
  147. package/dist/components/ListView/index.d.ts +0 -2
  148. package/dist/components/ListView/index.js +0 -2
  149. package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
  150. package/dist/components/NodeGraph/BaseNode.js +0 -30
  151. package/dist/components/NodeGraph/Edge.svelte +0 -60
  152. package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
  153. package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
  154. package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
  155. package/dist/components/NodeGraph/Node.svelte +0 -100
  156. package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
  157. package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
  158. package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
  159. package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
  160. package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
  161. package/dist/components/NodeGraph/NodePort.svelte +0 -75
  162. package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
  163. package/dist/components/NodeGraph/decorators.d.ts +0 -81
  164. package/dist/components/NodeGraph/decorators.js +0 -148
  165. package/dist/components/NodeGraph/index.d.ts +0 -9
  166. package/dist/components/NodeGraph/index.js +0 -9
  167. package/dist/components/NodeGraph/types.d.ts +0 -94
  168. package/dist/components/NodeGraph/types.js +0 -33
  169. package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
  170. package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
  171. package/dist/components/NotesEditor/index.d.ts +0 -1
  172. package/dist/components/NotesEditor/index.js +0 -1
  173. package/dist/components/NumberInput/index.d.ts +0 -1
  174. package/dist/components/NumberInput/index.js +0 -1
  175. package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
  176. package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
  177. package/dist/components/PasswordInput/index.d.ts +0 -1
  178. package/dist/components/PasswordInput/index.js +0 -1
  179. package/dist/components/Popup/index.d.ts +0 -1
  180. package/dist/components/Popup/index.js +0 -1
  181. package/dist/components/Rating/Rating.svelte.d.ts +0 -7
  182. package/dist/components/Rating/index.d.ts +0 -1
  183. package/dist/components/Rating/index.js +0 -1
  184. package/dist/components/ScrollView/ScrollView.svelte +0 -285
  185. package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
  186. package/dist/components/ScrollView/index.d.ts +0 -1
  187. package/dist/components/ScrollView/index.js +0 -1
  188. package/dist/components/SearchInput/index.d.ts +0 -1
  189. package/dist/components/SearchInput/index.js +0 -1
  190. package/dist/components/Select/index.d.ts +0 -1
  191. package/dist/components/Select/index.js +0 -1
  192. package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
  193. package/dist/components/TextArea/index.d.ts +0 -1
  194. package/dist/components/TextArea/index.js +0 -1
  195. package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
  196. package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
  197. package/dist/components/ThemeSelector/index.d.ts +0 -1
  198. package/dist/components/ThemeSelector/index.js +0 -1
  199. package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
  200. package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
  201. package/dist/components/TooltipHandler/index.d.ts +0 -1
  202. package/dist/components/TooltipHandler/index.js +0 -1
  203. package/dist/styles/sizes.d.ts +0 -78
  204. package/dist/styles/sizes.js +0 -120
  205. package/dist/styles/variants.d.ts +0 -106
  206. package/dist/styles/variants.js +0 -194
  207. package/dist/types/ManagerTypes.d.ts +0 -42
  208. /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
@@ -1,9 +1,12 @@
1
1
  <script lang="ts">
2
- import Input from '../Input/Input.svelte';
3
- import { faEnvelope } from '@fortawesome/free-solid-svg-icons';
4
- import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import Input from "./Input.svelte";
3
+ import Email from "~icons/mdi/email-outline";
4
+ import type { HTMLInputAttributes } from "svelte/elements";
5
+ import type { Size } from "../types/sizes.js";
6
+ import type { Responsive } from "../types/responsive.js";
7
+ import type { Variant } from "../types/variants.js";
5
8
 
6
- interface Props extends Omit<HTMLInputAttributes, 'type'> {
9
+ interface Props extends Omit<HTMLInputAttributes, "type" | "size"> {
7
10
  value: string;
8
11
  class?: string;
9
12
  inputClass?: string;
@@ -11,6 +14,10 @@
11
14
  valid?: boolean;
12
15
  touched?: boolean;
13
16
  element?: HTMLInputElement;
17
+ size?: Responsive<Size>;
18
+ /** Shared axes — forwarded to the underlying Input. */
19
+ variant?: Variant;
20
+ borderless?: boolean;
14
21
  required?: boolean;
15
22
  }
16
23
 
@@ -26,8 +33,9 @@
26
33
 
27
34
  // Default email validation
28
35
  const defaultEmailValidator = (value: string) => {
29
- if (required && !value) return 'Email is required';
30
- if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Please enter a valid email address';
36
+ if (required && !value) return "Email is required";
37
+ if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
38
+ return "Please enter a valid email address";
31
39
  return true;
32
40
  };
33
41
 
@@ -41,7 +49,8 @@
41
49
  bind:touched
42
50
  bind:element
43
51
  type="email"
44
- icon={faEnvelope}
45
52
  validate={emailValidator}
46
53
  {...restProps}
47
- />
54
+ >
55
+ {#snippet icon()}<Email />{/snippet}
56
+ </Input>
@@ -0,0 +1,21 @@
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
+ required?: boolean;
18
+ }
19
+ declare const EmailInput: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
20
+ type EmailInput = ReturnType<typeof EmailInput>;
21
+ export default EmailInput;
@@ -0,0 +1,369 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
4
+ import type { Size } from "../types/sizes.js";
5
+ import { responsiveClasses, type Responsive } from "../types/responsive.js";
6
+ import type { Variant } from "../types/variants.js";
7
+
8
+ // Omit the native `size` attribute (character width, a number) — we repurpose `size` as the
9
+ // shared sm/md/lg axis; the native one is meaningless on our flex-based input anyway.
10
+ interface Props extends Omit<HTMLInputAttributes, "size"> {
11
+ /** Accepts number too so remote-form field spreads (`{...field.as(...)}`) type-check. */
12
+ value?: string | number;
13
+ /** Icon slot — consumer-rendered (e.g. an MDI `~icons/mdi/*` component). Styled by the lib. */
14
+ icon?: Snippet;
15
+ /** Trailing controls rendered inside the bordered wrapper, right edge (e.g. clear/help buttons).
16
+ Lets compound components (Query, Select…) reuse Input's frame instead of re-rolling it. */
17
+ actions?: Snippet;
18
+ iconPosition?: "left" | "right";
19
+ size?: Responsive<Size>;
20
+ /** Colour axis (shared with every component). Tints the field's border like Card: the default
21
+ * `secondary` is the neutral strong edge; other variants use that colour at the 30% tint-border,
22
+ * strengthening on hover/focus. A validation error still overrides with the error colour. */
23
+ variant?: Variant;
24
+ /** Drop the visible frame while keeping the 1px box + focus outline: the border goes transparent
25
+ * (the field's background) and the hover/focus border-darkening is suppressed. Defaults `false` —
26
+ * an Input reads as a bordered field. A validation error still shows its border. */
27
+ borderless?: boolean;
28
+ iconClickable?: boolean;
29
+ onIconClick?: () => void;
30
+ class?: string;
31
+ inputClass?: string;
32
+ validate?: (value: string) => boolean | string;
33
+ valid?: boolean;
34
+ touched?: boolean;
35
+ element?: HTMLInputElement;
36
+ }
37
+
38
+ let {
39
+ value = $bindable(),
40
+ icon,
41
+ actions,
42
+ iconPosition = "right",
43
+ size = "md",
44
+ variant = "secondary",
45
+ borderless = false,
46
+ iconClickable = false,
47
+ onIconClick,
48
+ class: containerClass = "",
49
+ inputClass = "",
50
+ validate,
51
+ valid = $bindable(true),
52
+ touched = $bindable(false),
53
+ element = $bindable(),
54
+ type = "text",
55
+ disabled = false,
56
+ readonly = false,
57
+ // Pulled out of restProps so the wrapping handlers below can't be clobbered by the
58
+ // `{...restProps}` spread on the <input>; we still forward the consumer's callbacks.
59
+ oninput: oninputProp,
60
+ onfocus: onfocusProp,
61
+ ...restProps
62
+ }: Props = $props();
63
+
64
+ let inputElement: HTMLInputElement;
65
+
66
+ // Sync element binding
67
+ $effect(() => {
68
+ element = inputElement;
69
+ });
70
+
71
+ // Validation logic
72
+ let validationResult = $state<boolean | string>(true);
73
+
74
+ function runValidation() {
75
+ if (!validate) {
76
+ valid = true;
77
+ validationResult = true;
78
+ return;
79
+ }
80
+
81
+ const result = validate(String(value ?? ""));
82
+ validationResult = result;
83
+ valid = result === true;
84
+ }
85
+
86
+ // Run validation when value changes
87
+ $effect(() => {
88
+ runValidation();
89
+ });
90
+
91
+ // Size axis (see types/sizes.ts): control height, embedded icon-box, text, and svg size.
92
+ // Per-Size class maps → responsiveClasses() handles scalar OR { base, xs, … }. Icons: 14/16/20px.
93
+ const heightMap: Record<Size, string> = { sm: "h-6", md: "h-8", lg: "h-10" };
94
+ const iconBoxMap: Record<Size, string> = { sm: "w-6 h-6", md: "w-8 h-8", lg: "w-10 h-10" };
95
+ const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
96
+ const svgMap: Record<Size, string> = { sm: "w-3.5 h-3.5", md: "w-4 h-4", lg: "w-5 h-5" };
97
+ // Border colour by variant — ONE calculation for EVERY variant (no `secondary`/`-strong` special
98
+ // case): the variant token at `--ui-tint-border` (30%) at rest, the same token at 55% on hover/focus,
99
+ // always mixed toward transparent (the tint language, like Alert/Badge/Card). Literal classes so
100
+ // Tailwind can generate them (incl. the hover/focus states). ghost/neutral resolve to --ui-color-text.
101
+ const borderVariantClass: Record<Variant, string> = {
102
+ secondary:
103
+ "[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)]",
104
+ primary:
105
+ "[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border-hover),transparent)]",
106
+ danger:
107
+ "[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border-hover),transparent)]",
108
+ info: "[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border-hover),transparent)]",
109
+ success:
110
+ "[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border-hover),transparent)]",
111
+ warning:
112
+ "[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border-hover),transparent)]",
113
+ neutral:
114
+ "[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)]",
115
+ ghost:
116
+ "[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)] focus-within:[border-color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border-hover),transparent)]",
117
+ };
118
+
119
+ // Placeholder colour tracks the variant too — muted, matching each variant's RESTING border colour
120
+ // (secondary = the neutral strong edge; others = their 30% tint-border). Literal classes so Tailwind
121
+ // emits them; applied regardless of `borderless`, so a frameless variant field still reads as coloured.
122
+ const placeholderVariantClass: Record<Variant, string> = {
123
+ secondary:
124
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]",
125
+ primary:
126
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border),transparent)]",
127
+ danger:
128
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border),transparent)]",
129
+ info: "placeholder:[color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border),transparent)]",
130
+ success:
131
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border),transparent)]",
132
+ warning:
133
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border),transparent)]",
134
+ neutral:
135
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
136
+ ghost:
137
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
138
+ };
139
+
140
+ const heightClass = $derived(responsiveClasses(size, heightMap));
141
+ const iconBoxClass = $derived(responsiveClasses(size, iconBoxMap));
142
+ const textClass = $derived(responsiveClasses(size, textMap));
143
+ const svgClass = $derived(responsiveClasses(size, svgMap));
144
+
145
+ // Computed classes
146
+ let containerClasses = $derived.by(() => {
147
+ const base = "relative w-full";
148
+ return `${base} ${containerClass}`.trim();
149
+ });
150
+
151
+ let wrapperClasses = $derived.by(() => {
152
+ const base = `w-full ${heightClass} flex items-center [border-radius:var(--ui-border-radius)]`;
153
+
154
+ // Calculate padding based on icon presence and position
155
+ let paddingClasses = "";
156
+ if (icon) {
157
+ // Icon present - only pad the side without icon
158
+ if (iconPosition === "left") {
159
+ paddingClasses = "pl-0 pr-2"; // Icon on left, pad right only
160
+ } else {
161
+ paddingClasses = "pl-2 pr-0"; // Icon on right, pad left only
162
+ }
163
+ } else {
164
+ // No icon - pad both sides
165
+ paddingClasses = "px-2";
166
+ }
167
+
168
+ // Border colour comes from the variant map (the shared tint scheme — token @ --ui-tint-border at
169
+ // rest, @ --ui-tint-border-hover on hover/focus — same as Card/DataTable/TextArea, so every bordered
170
+ // surface stays uniform under any theme). `borderless` overrides it with transparent (the field's
171
+ // own background) — the 1px box stays, so the field never jumps, and the focus outline still fires.
172
+ const frameStyles = borderless ? "[border-color:transparent]" : borderVariantClass[variant];
173
+ const themeStyles = [
174
+ "bg-transparent",
175
+ "border", // 1px — matches --ui-border (List/TextArea); uniform across the library
176
+ paddingClasses,
177
+ frameStyles,
178
+ "focus-within:[outline:2px_solid_color-mix(in_srgb,currentColor_70%,transparent)]",
179
+ "focus-within:[outline-offset:0px]",
180
+ ].join(" ");
181
+
182
+ // Validation state overrides
183
+ let validationStyles = "";
184
+ if (touched && !valid) {
185
+ validationStyles =
186
+ "[border-color:var(--ui-color-error)] focus-within:[ring-color:var(--ui-color-error)]";
187
+ }
188
+
189
+ // Disabled state — borders are uniformly 1px now, so no padding compensation is needed.
190
+ // (readonly renders like a normal field: same 1px frame + hover/focus glow, e.g. Select's trigger.)
191
+ let disabledStyles = "";
192
+ if (disabled) {
193
+ disabledStyles =
194
+ "![background-color:color-mix(in_srgb,var(--ui-color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] cursor-not-allowed";
195
+ }
196
+
197
+ return `${base} ${themeStyles} ${validationStyles} ${disabledStyles}`.trim();
198
+ });
199
+
200
+ let inputClasses = $derived.by(() => {
201
+ const base = `flex-1 h-full bg-transparent border-none outline-none ${textClass} leading-tight p-0 min-w-0`;
202
+
203
+ // Text uses the theme's text token; placeholder is a muted mix of
204
+ // it (matches DaisyUI's `base-content/50` convention). Not tied
205
+ // to --ui-color-secondary anymore so a theme's accent palette doesn't
206
+ // leak into every input's placeholder.
207
+ const themeStyles = [
208
+ "[color:var(--ui-color-text)]",
209
+ placeholderVariantClass[variant],
210
+ "disabled:bg-transparent",
211
+ "disabled:[color:color-mix(in_srgb,var(--ui-color-text)_40%,transparent)]",
212
+ "disabled:cursor-not-allowed",
213
+ ].join(" ");
214
+
215
+ return `${base} ${themeStyles} ${inputClass}`.trim();
216
+ });
217
+
218
+ let iconClasses = $derived.by(() => {
219
+ const base = `${iconBoxClass} flex items-center justify-center flex-shrink-0 select-none`;
220
+
221
+ const interactive = iconClickable
222
+ ? "cursor-pointer focus:outline-none transition-colors group"
223
+ : "pointer-events-none";
224
+
225
+ return `${base} ${interactive}`.trim();
226
+ });
227
+
228
+ // Event handlers
229
+ function handleInput(event: Event & { currentTarget: EventTarget & HTMLInputElement }) {
230
+ const target = event.target as HTMLInputElement;
231
+ value = target.value;
232
+ oninputProp?.(event);
233
+ }
234
+
235
+ function handleFocus(event: FocusEvent & { currentTarget: EventTarget & HTMLInputElement }) {
236
+ touched = true;
237
+ onfocusProp?.(event);
238
+ }
239
+
240
+ function handleIconKeydown(event: KeyboardEvent) {
241
+ if (event.key === "Enter" || event.key === " ") {
242
+ event.preventDefault();
243
+ onIconClick?.();
244
+ }
245
+ }
246
+
247
+ function handleIconMouseDown(event: MouseEvent) {
248
+ event.preventDefault(); // Prevent text selection on double-click
249
+ event.stopPropagation(); // Prevent event from bubbling
250
+ }
251
+
252
+ function handleIconSelectStart(event: Event) {
253
+ event.preventDefault(); // Prevent text selection
254
+ return false;
255
+ }
256
+
257
+ function handleIconDoubleClick(event: MouseEvent) {
258
+ event.preventDefault();
259
+ event.stopPropagation();
260
+ return false;
261
+ }
262
+
263
+ // Override browser validation
264
+ $effect(() => {
265
+ if (inputElement) {
266
+ inputElement.setCustomValidity("");
267
+ if (touched && !valid && typeof validationResult === "string") {
268
+ inputElement.setCustomValidity(validationResult);
269
+ }
270
+ }
271
+ });
272
+ </script>
273
+
274
+ <!-- Styled icon box: the consumer renders any icon component in the `icon` snippet; the lib controls
275
+ size + color here (MDI icons use currentColor and fill the box), so it works with any icon set. -->
276
+ {#snippet iconBox()}
277
+ <span
278
+ class="inline-flex {svgClass} items-center justify-center [color:var(--ui-color-secondary)] group-hover:[color:color-mix(in_srgb,var(--ui-color-secondary)_80%,black)] group-focus:[color:color-mix(in_srgb,currentColor_70%,transparent)] transition-colors [&_svg]:w-full [&_svg]:h-full"
279
+ >
280
+ {@render icon?.()}
281
+ </span>
282
+ {/snippet}
283
+
284
+ <div class={containerClasses}>
285
+ <div class={wrapperClasses} style="user-select: none;">
286
+ {#if icon && iconPosition === "left"}
287
+ {#if iconClickable}
288
+ <div
289
+ class={iconClasses}
290
+ onclick={onIconClick}
291
+ onkeydown={handleIconKeydown}
292
+ onmousedown={handleIconMouseDown}
293
+ ondblclick={handleIconDoubleClick}
294
+ onselectstart={handleIconSelectStart}
295
+ role="button"
296
+ tabindex="0"
297
+ style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
298
+ >
299
+ {@render iconBox()}
300
+ </div>
301
+ {:else}
302
+ <div class={iconClasses}>
303
+ {@render iconBox()}
304
+ </div>
305
+ {/if}
306
+ {/if}
307
+
308
+ <input
309
+ bind:this={inputElement}
310
+ {type}
311
+ {value}
312
+ {disabled}
313
+ {readonly}
314
+ oninput={handleInput}
315
+ onfocus={handleFocus}
316
+ {...restProps}
317
+ class="{inputClasses} ui-input"
318
+ style="border: none !important; user-select: text !important;"
319
+ />
320
+
321
+ {#if icon && iconPosition === "right"}
322
+ {#if iconClickable}
323
+ <div
324
+ class={iconClasses}
325
+ onclick={onIconClick}
326
+ onkeydown={handleIconKeydown}
327
+ onmousedown={handleIconMouseDown}
328
+ ondblclick={handleIconDoubleClick}
329
+ onselectstart={handleIconSelectStart}
330
+ role="button"
331
+ tabindex="0"
332
+ style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
333
+ >
334
+ {@render iconBox()}
335
+ </div>
336
+ {:else}
337
+ <div class={iconClasses}>
338
+ {@render iconBox()}
339
+ </div>
340
+ {/if}
341
+ {/if}
342
+
343
+ {#if actions}
344
+ <div class="flex h-full shrink-0 items-center gap-1">
345
+ {@render actions()}
346
+ </div>
347
+ {/if}
348
+ </div>
349
+ </div>
350
+
351
+ <style>
352
+ /* Kill the UA focus ring/box-shadow without inline !important (which would also defeat the
353
+ autofill override below, since box-shadow and -webkit-box-shadow are the same property). */
354
+ .ui-input {
355
+ box-shadow: none !important;
356
+ }
357
+ /* Theme the browser autofill so it fills the whole field in our background colour (no inset blue
358
+ band) and uses our text colour, in both light and dark. More specific than `.ui-input`, so it
359
+ wins. The huge inset shadow paints over the UA-yellow/blue highlight. */
360
+ .ui-input:-webkit-autofill,
361
+ .ui-input:-webkit-autofill:hover,
362
+ .ui-input:-webkit-autofill:focus,
363
+ .ui-input:-webkit-autofill:active {
364
+ -webkit-box-shadow: 0 0 0 1000px var(--ui-color-background) inset !important;
365
+ -webkit-text-fill-color: var(--ui-color-text) !important;
366
+ caret-color: var(--ui-color-text);
367
+ transition: background-color 9999s ease-in-out 0s; /* belt-and-suspenders against the fade */
368
+ }
369
+ </style>
@@ -0,0 +1,35 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+ import type { Size } from "../types/sizes.js";
4
+ import { type Responsive } from "../types/responsive.js";
5
+ import type { Variant } from "../types/variants.js";
6
+ interface Props extends Omit<HTMLInputAttributes, "size"> {
7
+ /** Accepts number too so remote-form field spreads (`{...field.as(...)}`) type-check. */
8
+ value?: string | number;
9
+ /** Icon slot — consumer-rendered (e.g. an MDI `~icons/mdi/*` component). Styled by the lib. */
10
+ icon?: Snippet;
11
+ /** Trailing controls rendered inside the bordered wrapper, right edge (e.g. clear/help buttons).
12
+ Lets compound components (Query, Select…) reuse Input's frame instead of re-rolling it. */
13
+ actions?: Snippet;
14
+ iconPosition?: "left" | "right";
15
+ size?: Responsive<Size>;
16
+ /** Colour axis (shared with every component). Tints the field's border like Card: the default
17
+ * `secondary` is the neutral strong edge; other variants use that colour at the 30% tint-border,
18
+ * strengthening on hover/focus. A validation error still overrides with the error colour. */
19
+ variant?: Variant;
20
+ /** Drop the visible frame while keeping the 1px box + focus outline: the border goes transparent
21
+ * (the field's background) and the hover/focus border-darkening is suppressed. Defaults `false` —
22
+ * an Input reads as a bordered field. A validation error still shows its border. */
23
+ borderless?: boolean;
24
+ iconClickable?: boolean;
25
+ onIconClick?: () => void;
26
+ class?: string;
27
+ inputClass?: string;
28
+ validate?: (value: string) => boolean | string;
29
+ valid?: boolean;
30
+ touched?: boolean;
31
+ element?: HTMLInputElement;
32
+ }
33
+ declare const Input: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
34
+ type Input = ReturnType<typeof Input>;
35
+ export default Input;