@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.3.e16df24

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 +180 -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,10 +1,16 @@
1
1
  <script lang="ts">
2
- import Icon from "../Icon/Icon.svelte";
3
- import { faCopy } from "@fortawesome/free-solid-svg-icons";
2
+ import ContentCopy from "~icons/mdi/content-copy";
4
3
  import type { HTMLTextareaAttributes } from "svelte/elements";
4
+ import type { Size } from "../types/sizes.js";
5
+ import { responsiveClasses, resolveScalar, type Responsive } from "../types/responsive.js";
6
+ import type { Variant } from "../types/variants.js";
5
7
  import { DEV } from "esm-env";
6
8
 
7
- interface Props extends HTMLTextareaAttributes {
9
+ type TextAreaCopy = "none" | "selection" | "button";
10
+
11
+ // Omit the native `oncopy` DOM handler — we repurpose it as a copy-button callback (see below),
12
+ // the same way TabStrip omits the native `onselect`.
13
+ interface Props extends Omit<HTMLTextareaAttributes, "oncopy"> {
8
14
  value?: string;
9
15
  lines?: string[];
10
16
  class?: string;
@@ -16,10 +22,62 @@
16
22
  minRows?: number;
17
23
  maxRows?: number;
18
24
  lineHeightRem?: number;
25
+ size?: Responsive<Size>;
26
+ /** Colour axis — tints the border + placeholder with the SAME calc as Input (token @ 30% at rest,
27
+ * 55% on hover/focus, mixed toward transparent). No `secondary`/`-strong` special case. */
28
+ variant?: Variant;
29
+ /** Drop the visible frame while keeping the 1px box + focus outline (border → transparent). */
30
+ borderless?: boolean;
19
31
  required?: boolean;
20
- allowCopy?: boolean;
32
+ /** How the text can be copied — one exclusive mode:
33
+ * `'selection'` (default) — copy via native text selection only, no button;
34
+ * `'button'` — also show the copy-to-clipboard button;
35
+ * `'none'` — copying disabled: no button and the text is not selectable.
36
+ * Orthogonal to `readonly`/`disabled` (all other behaviour unchanged). */
37
+ copy?: TextAreaCopy;
38
+ /** Fires after the copy button successfully writes to the clipboard, with the copied text.
39
+ * Lets a consumer confirm the copy (e.g. `oncopy={() => toast.success('Copied to clipboard')}`).
40
+ * Not called for native selection-copy, only the `copy="button"` action. */
41
+ oncopy?: (text: string) => void;
21
42
  }
22
43
 
44
+ // Border + placeholder colour by variant — IDENTICAL formula to Input (one calc for every variant):
45
+ // token @ --ui-tint-border (30%) at rest, token @ 55% on hover/focus, mixed toward transparent.
46
+ const borderVariantClass: Record<Variant, string> = {
47
+ secondary:
48
+ "[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)]",
49
+ primary:
50
+ "[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)]",
51
+ danger:
52
+ "[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)]",
53
+ 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)]",
54
+ success:
55
+ "[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)]",
56
+ warning:
57
+ "[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)]",
58
+ neutral:
59
+ "[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)]",
60
+ ghost:
61
+ "[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)]",
62
+ };
63
+ const placeholderVariantClass: Record<Variant, string> = {
64
+ secondary:
65
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]",
66
+ primary:
67
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-primary)_var(--ui-tint-border),transparent)]",
68
+ danger:
69
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-error)_var(--ui-tint-border),transparent)]",
70
+ info: "placeholder:[color:color-mix(in_srgb,var(--ui-color-info)_var(--ui-tint-border),transparent)]",
71
+ success:
72
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-success)_var(--ui-tint-border),transparent)]",
73
+ warning:
74
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-warning)_var(--ui-tint-border),transparent)]",
75
+ neutral:
76
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
77
+ ghost:
78
+ "placeholder:[color:color-mix(in_srgb,var(--ui-color-text)_var(--ui-tint-border),transparent)]",
79
+ };
80
+
23
81
  let {
24
82
  value = $bindable(),
25
83
  lines = $bindable(),
@@ -32,13 +90,27 @@
32
90
  disabled = false,
33
91
  minRows = 3,
34
92
  maxRows,
35
- lineHeightRem = 1.5,
93
+ lineHeightRem,
94
+ size = "md",
95
+ variant = "secondary",
96
+ borderless = false,
36
97
  required = false,
37
- allowCopy = $bindable(true),
98
+ copy = "selection",
99
+ oncopy,
38
100
  readonly = false,
39
101
  ...restProps
40
102
  }: Props = $props();
41
103
 
104
+ // Size axis (see types/sizes.ts): text size, wrapper padding, and the line-height the autosize
105
+ // math keys off (so smaller sizes aren't airy). An explicit lineHeightRem still wins.
106
+ const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
107
+ const padMap: Record<Size, string> = { sm: "py-0.5 px-2", md: "py-1 px-2", lg: "py-2 px-3" };
108
+ const textClass = $derived(responsiveClasses(size, textMap));
109
+ const padClass = $derived(responsiveClasses(size, padMap));
110
+ // Autosize math is JS-numeric (not a class), so it keys off the base tier of a responsive size.
111
+ const lhMap: Record<Size, number> = { sm: 1.25, md: 1.5, lg: 1.75 };
112
+ const effLineHeightRem = $derived(lineHeightRem ?? lhMap[resolveScalar(size, "md")]);
113
+
42
114
  let textareaElement = $state<HTMLTextAreaElement>();
43
115
  let containerElement = $state<HTMLDivElement>();
44
116
  let isDragging = $state(false);
@@ -50,14 +122,10 @@
50
122
  // Development warnings for prop conflicts and empty content
51
123
  if (DEV) {
52
124
  if (value !== undefined && lines !== undefined) {
53
- console.warn(
54
- "TextArea: Both value and lines provided. Using value, ignoring lines."
55
- );
125
+ console.warn("TextArea: Both value and lines provided. Using value, ignoring lines.");
56
126
  }
57
127
  if (value === undefined && !lines) {
58
- console.warn(
59
- "TextArea: No content provided (value or lines). TextArea will be empty."
60
- );
128
+ console.warn("TextArea: No content provided (value or lines). TextArea will be empty.");
61
129
  }
62
130
  }
63
131
 
@@ -67,7 +135,7 @@
67
135
 
68
136
  // Content precedence: value > lines
69
137
  let effectiveContent = $derived(
70
- hasValueProp ? value : hasLinesProp ? lines?.join("\n") || "" : ""
138
+ hasValueProp ? value : hasLinesProp ? lines?.join("\n") || "" : "",
71
139
  );
72
140
 
73
141
  // Handle bidirectional binding for lines
@@ -81,11 +149,12 @@
81
149
  });
82
150
 
83
151
  // Enforce minimum rows and handle maxRows auto-sizing
84
- const effectiveMinRows = Math.max(minRows, 2);
85
- const effectiveMaxRows = maxRows ?? Infinity;
86
- // disabled overrides both readonly and allowCopy
87
- const effectiveReadonly = disabled ? true : readonly;
88
- const effectiveAllowCopy = disabled ? false : allowCopy;
152
+ const effectiveMinRows = $derived(Math.max(minRows, 2));
153
+ const effectiveMaxRows = $derived(maxRows ?? Infinity);
154
+ // disabled overrides readonly + the copy mode. `button` shows the button; `none` also blocks selection.
155
+ const effectiveReadonly = $derived(disabled ? true : readonly);
156
+ const effectiveCopyable = $derived(disabled ? false : copy === "button");
157
+ const effectiveSelectable = $derived(disabled ? false : copy !== "none");
89
158
 
90
159
  // Calculate initial rows based on content
91
160
  // NOTE: This only counts explicit line breaks, not wrapped lines.
@@ -96,10 +165,7 @@
96
165
  // Count actual lines in the content (newline characters only)
97
166
  const lineCount = effectiveContent.split("\n").length;
98
167
  // Clamp between min and max
99
- return Math.min(
100
- Math.max(lineCount, effectiveMinRows, minRows),
101
- effectiveMaxRows
102
- );
168
+ return Math.min(Math.max(lineCount, effectiveMinRows, minRows), effectiveMaxRows);
103
169
  }
104
170
  // Default to minRows if no content
105
171
  return Math.max(minRows, effectiveMinRows);
@@ -137,18 +203,13 @@
137
203
  // Auto-resize functionality
138
204
  function updateHeight() {
139
205
  if (!textareaElement) return;
140
-
141
-
142
206
 
143
207
  // Get line height - calculate from the prop value, ensuring whole pixels
144
208
  if (lineHeight === 0) {
145
- const rootFontSize = parseFloat(
146
- getComputedStyle(document.documentElement).fontSize
147
- );
209
+ const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
148
210
  // Round to nearest whole pixel, then convert back to rem for consistency
149
- lineHeight = Math.round(lineHeightRem * rootFontSize);
211
+ lineHeight = Math.round(effLineHeightRem * rootFontSize);
150
212
  actualLineHeightRem = lineHeight / rootFontSize;
151
-
152
213
  }
153
214
 
154
215
  // Store current scroll position
@@ -157,24 +218,21 @@
157
218
  // Force browser reflow by completely resetting styles
158
219
  textareaElement.style.height = "";
159
220
  textareaElement.style.overflow = "";
160
-
221
+
161
222
  // Force a layout reflow
162
223
  textareaElement.offsetHeight;
163
-
224
+
164
225
  // Now set to auto for measurement
165
226
  textareaElement.style.height = "auto";
166
227
  textareaElement.style.overflow = "hidden";
167
-
228
+
168
229
  // Force another reflow to ensure measurement is accurate
169
230
  const scrollHeight = textareaElement.scrollHeight;
170
231
 
171
232
  // Calculate rows needed
172
233
  const neededRows = Math.ceil(scrollHeight / lineHeight);
173
- const actualRows = Math.min(
174
- Math.max(neededRows, effectiveMinRows),
175
- effectiveMaxRows
176
- );
177
-
234
+ const actualRows = Math.min(Math.max(neededRows, effectiveMinRows), effectiveMaxRows);
235
+
178
236
  // Update maxRows exceeded state
179
237
  maxRowsExceeded = neededRows > effectiveMaxRows;
180
238
 
@@ -195,8 +253,7 @@
195
253
  isScrollable = false;
196
254
  } else {
197
255
  // Only scrollable if content actually overflows the set height
198
- isScrollable =
199
- textareaElement.scrollHeight > textareaElement.clientHeight;
256
+ isScrollable = textareaElement.scrollHeight > textareaElement.clientHeight;
200
257
  }
201
258
  } else if (effectiveReadonly) {
202
259
  // Readonly textareas are never scrollable in our context
@@ -229,10 +286,7 @@
229
286
  setTimeout(() => updateHeight(), 0);
230
287
 
231
288
  // Check if mouse is already over the element on mount (scrollbar for readonly too)
232
- if (
233
- containerElement?.matches(":hover") ||
234
- textareaElement?.matches(":hover")
235
- ) {
289
+ if (containerElement?.matches(":hover") || textareaElement?.matches(":hover")) {
236
290
  hovered = true;
237
291
  updateScrollbar();
238
292
  }
@@ -246,14 +300,14 @@
246
300
  });
247
301
 
248
302
  let wrapperClasses = $derived.by(() => {
249
- const base = "relative w-full rounded-md border-2 py-1 px-2";
303
+ const base = `relative w-full [border-radius:var(--ui-border-radius)] border ${padClass}`;
250
304
 
251
- // Use new semantic color system matching Input
305
+ // Border colour from the variant map (same calc as Input); `borderless` → transparent (its own
306
+ // background), keeping the 1px box + focus outline.
307
+ const frameStyles = borderless ? "[border-color:transparent]" : borderVariantClass[variant];
252
308
  const themeStyles = [
253
309
  "bg-transparent",
254
- "[border-color:var(--color-secondary)]",
255
- "hover:[border-color:color-mix(in_srgb,var(--color-secondary)_80%,black)]",
256
- "focus-within:[border-color:color-mix(in_srgb,var(--color-secondary)_90%,black)]",
310
+ frameStyles,
257
311
  "focus-within:[outline:2px_solid_color-mix(in_srgb,currentColor_70%,transparent)]",
258
312
  "focus-within:[outline-offset:0px]",
259
313
  ].join(" ");
@@ -262,37 +316,34 @@
262
316
  let validationStyles = "";
263
317
  if (touched && !valid) {
264
318
  validationStyles =
265
- "[border-color:var(--color-danger)] focus-within:[outline:2px_solid_color-mix(in_srgb,var(--color-danger)_70%,transparent)]";
319
+ "[border-color:var(--ui-color-error)] focus-within:[outline:2px_solid_color-mix(in_srgb,var(--ui-color-error)_70%,transparent)]";
266
320
  }
267
321
 
268
- // Disabled and readonly state
322
+ // Disabled state borders are uniformly 1px, so no padding compensation is needed.
269
323
  const disabledStyles = disabled
270
- ? "![background-color:color-mix(in_srgb,var(--color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--color-secondary)_50%,transparent)] !border !py-[calc(0.25rem+1px)] !px-[calc(0.5rem+1px)] cursor-not-allowed"
271
- : effectiveReadonly
272
- ? "[border-color:var(--color-secondary)] !border !py-[calc(0.25rem+1px)] !px-[calc(0.5rem+1px)]"
273
- : "";
324
+ ? "![background-color:color-mix(in_srgb,var(--ui-color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_50%,transparent)] cursor-not-allowed"
325
+ : "";
274
326
 
275
327
  return `${base} ${themeStyles} ${validationStyles} ${disabledStyles}`.trim();
276
328
  });
277
329
 
278
330
  let textareaClasses = $derived.by(() => {
279
- const base =
280
- "w-full bg-transparent border-none outline-none text-sm resize-none custom-textarea";
331
+ const base = `w-full bg-transparent border-none outline-none ${textClass} resize-none custom-textarea`;
281
332
 
282
333
  // Use new semantic color system
283
334
  const themeStyles = [
284
- "[color:var(--color-text)]",
285
- "placeholder:[color:var(--color-secondary)]",
335
+ "[color:var(--ui-color-text)]",
336
+ placeholderVariantClass[variant],
286
337
  "disabled:bg-transparent",
287
- "disabled:[color:color-mix(in_srgb,var(--color-text)_40%,transparent)]",
338
+ "disabled:[color:color-mix(in_srgb,var(--ui-color-text)_40%,transparent)]",
288
339
  "disabled:cursor-not-allowed",
289
340
  ].join(" ");
290
341
 
291
- // Padding adjustment for copy icon
292
- const padding = effectiveAllowCopy ? "pr-8" : "";
342
+ // Padding adjustment for the copy icon (only when the button is shown)
343
+ const padding = effectiveCopyable ? "pr-8" : "";
293
344
 
294
345
  // Cursor style based on allowCopy
295
- const cursor = effectiveAllowCopy ? "" : "cursor-default";
346
+ const cursor = effectiveSelectable ? "" : "cursor-default";
296
347
 
297
348
  return `${base} ${themeStyles} ${padding} ${cursor} ${textareaClass}`.trim();
298
349
  });
@@ -300,8 +351,7 @@
300
351
  let copyIconClasses = $derived.by(() => {
301
352
  const base =
302
353
  "absolute top-0 right-0 w-8 h-8 flex items-center justify-center flex-shrink-0 select-none z-20";
303
- const interactive =
304
- "cursor-pointer focus:outline-none transition-colors group";
354
+ const interactive = "cursor-pointer focus:outline-none transition-colors group";
305
355
 
306
356
  return `${base} ${interactive}`.trim();
307
357
  });
@@ -324,7 +374,7 @@
324
374
  }
325
375
 
326
376
  function handleFocus(event: FocusEvent) {
327
- if (!effectiveAllowCopy) {
377
+ if (!effectiveSelectable) {
328
378
  // Prevent focus when allowCopy is false
329
379
  event.preventDefault();
330
380
  if (textareaElement) {
@@ -341,7 +391,7 @@
341
391
  if (!effectiveContent) return;
342
392
  try {
343
393
  await navigator.clipboard.writeText(effectiveContent);
344
- // In a real app, you might show a toast notification here
394
+ oncopy?.(effectiveContent);
345
395
  } catch (err) {
346
396
  console.error("Failed to copy text: ", err);
347
397
  }
@@ -372,7 +422,7 @@
372
422
 
373
423
  // Prevent text selection when allowCopy is false
374
424
  function handleSelectStart(event: Event) {
375
- if (!effectiveAllowCopy) {
425
+ if (!effectiveSelectable) {
376
426
  event.preventDefault();
377
427
  return false;
378
428
  }
@@ -380,14 +430,14 @@
380
430
 
381
431
  // Additional handlers for comprehensive text selection prevention
382
432
  function handleDragStart(event: DragEvent) {
383
- if (!effectiveAllowCopy) {
433
+ if (!effectiveSelectable) {
384
434
  event.preventDefault();
385
435
  return false;
386
436
  }
387
437
  }
388
438
 
389
439
  function handleContextMenu(event: MouseEvent) {
390
- if (!effectiveAllowCopy) {
440
+ if (!effectiveSelectable) {
391
441
  event.preventDefault();
392
442
  return false;
393
443
  }
@@ -397,7 +447,7 @@
397
447
  // Handle restProps mousedown first
398
448
  if (restProps.onmousedown) restProps.onmousedown(event as any);
399
449
 
400
- if (!effectiveAllowCopy) {
450
+ if (!effectiveSelectable) {
401
451
  // Prevent clicking inside entirely - no focus, no selection
402
452
  event.preventDefault();
403
453
  event.stopPropagation();
@@ -408,12 +458,9 @@
408
458
  // Handle restProps keydown first
409
459
  if (restProps.onkeydown) restProps.onkeydown(event as any);
410
460
 
411
- if (!effectiveAllowCopy) {
461
+ if (!effectiveSelectable) {
412
462
  // Prevent Ctrl+A (select all) and other selection shortcuts
413
- if (
414
- (event.ctrlKey || event.metaKey) &&
415
- (event.key === "a" || event.key === "A")
416
- ) {
463
+ if ((event.ctrlKey || event.metaKey) && (event.key === "a" || event.key === "A")) {
417
464
  event.preventDefault();
418
465
  return false;
419
466
  }
@@ -454,16 +501,11 @@
454
501
  return;
455
502
  }
456
503
 
457
- const visibleRatio =
458
- textareaElement.clientHeight / textareaElement.scrollHeight;
504
+ const visibleRatio = textareaElement.clientHeight / textareaElement.scrollHeight;
459
505
  const trackHeight = textareaElement.clientHeight;
460
- thumbHeight = Math.max(
461
- 20,
462
- Math.min(trackHeight, visibleRatio * trackHeight)
463
- );
506
+ thumbHeight = Math.max(20, Math.min(trackHeight, visibleRatio * trackHeight));
464
507
 
465
- const maxScrollTop =
466
- textareaElement.scrollHeight - textareaElement.clientHeight;
508
+ const maxScrollTop = textareaElement.scrollHeight - textareaElement.clientHeight;
467
509
  if (maxScrollTop > 0) {
468
510
  const scrollRatio = textareaElement.scrollTop / maxScrollTop;
469
511
  const availableTrackHeight = trackHeight - thumbHeight;
@@ -485,9 +527,7 @@
485
527
  const currentScroll = textareaElement.scrollTop;
486
528
 
487
529
  // Calculate the nearest line boundary (ensure whole pixels)
488
- const nearestLineScroll = Math.round(
489
- Math.round(currentScroll / lineHeight) * lineHeight
490
- );
530
+ const nearestLineScroll = Math.round(Math.round(currentScroll / lineHeight) * lineHeight);
491
531
 
492
532
  // Only adjust if we're more than 0.5px away from a line boundary
493
533
  // Use a smaller threshold to reduce bounce but still snap effectively
@@ -515,8 +555,7 @@
515
555
 
516
556
  // Check if the scroll would stay within textarea bounds
517
557
  const currentScroll = textareaElement.scrollTop;
518
- const maxScrollTop =
519
- textareaElement.scrollHeight - textareaElement.clientHeight;
558
+ const maxScrollTop = textareaElement.scrollHeight - textareaElement.clientHeight;
520
559
  const scrollingDown = event.deltaY > 0;
521
560
  const scrollingUp = event.deltaY < 0;
522
561
 
@@ -544,10 +583,7 @@
544
583
  const currentLine = Math.round(currentScroll / lineHeight);
545
584
 
546
585
  // Calculate new line position
547
- const newLine = Math.max(
548
- 0,
549
- currentLine + (scrollDelta > 0 ? linesPerStep : -linesPerStep)
550
- );
586
+ const newLine = Math.max(0, currentLine + (scrollDelta > 0 ? linesPerStep : -linesPerStep));
551
587
  const newScrollTop = newLine * lineHeight;
552
588
 
553
589
  // Apply bounds checking
@@ -574,12 +610,11 @@
574
610
  const deltaY = event.clientY - dragStartY;
575
611
  const trackHeight = textareaElement.clientHeight - thumbHeight;
576
612
  const scrollRatio = deltaY / trackHeight;
577
- const maxScrollTop =
578
- textareaElement.scrollHeight - textareaElement.clientHeight;
613
+ const maxScrollTop = textareaElement.scrollHeight - textareaElement.clientHeight;
579
614
 
580
615
  textareaElement.scrollTop = Math.max(
581
616
  0,
582
- Math.min(maxScrollTop, dragStartScrollTop + scrollRatio * maxScrollTop)
617
+ Math.min(maxScrollTop, dragStartScrollTop + scrollRatio * maxScrollTop),
583
618
  );
584
619
  }
585
620
 
@@ -595,7 +630,7 @@
595
630
 
596
631
  // Initialize window dimensions
597
632
  $effect(() => {
598
- if (typeof window !== 'undefined') {
633
+ if (typeof window !== "undefined") {
599
634
  windowWidth = window.innerWidth;
600
635
  windowHeight = window.innerHeight;
601
636
 
@@ -604,8 +639,8 @@
604
639
  windowHeight = window.innerHeight;
605
640
  };
606
641
 
607
- window.addEventListener('resize', handleResize);
608
- return () => window.removeEventListener('resize', handleResize);
642
+ window.addEventListener("resize", handleResize);
643
+ return () => window.removeEventListener("resize", handleResize);
609
644
  }
610
645
  });
611
646
 
@@ -658,25 +693,22 @@
658
693
  {...restProps}
659
694
  class={textareaClasses}
660
695
  style="line-height: {actualLineHeightRem ||
661
- lineHeightRem}rem; margin: 0; padding: 0; vertical-align: top; {effectiveReadonly
696
+ effLineHeightRem}rem; margin: 0; padding: 0; vertical-align: top; {effectiveReadonly
662
697
  ? ''
663
- : 'scroll-snap-type: y mandatory; scroll-padding: 0; overscroll-behavior: contain;'}{effectiveAllowCopy
698
+ : 'scroll-snap-type: y mandatory; scroll-padding: 0; overscroll-behavior: contain;'}{effectiveSelectable
664
699
  ? ''
665
700
  : ' user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;'}"
666
701
  rows={initialRows}
667
702
  {disabled}
668
703
  readonly={effectiveReadonly}
669
704
  {required}
670
- tabindex={effectiveAllowCopy ? undefined : -1}
705
+ tabindex={effectiveSelectable ? undefined : -1}
671
706
  aria-invalid={touched && !valid ? "true" : "false"}
672
- aria-describedby={touched &&
673
- !valid &&
674
- typeof validationResult === "string"
707
+ aria-describedby={touched && !valid && typeof validationResult === "string"
675
708
  ? "textarea-error"
676
- : undefined}
677
- ></textarea>
709
+ : undefined}></textarea>
678
710
 
679
- {#if effectiveAllowCopy && effectiveContent}
711
+ {#if effectiveCopyable && effectiveContent}
680
712
  <div
681
713
  class={copyIconClasses}
682
714
  onclick={copyToClipboard}
@@ -689,9 +721,8 @@
689
721
  style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
690
722
  title="Copy to clipboard"
691
723
  >
692
- <Icon
693
- icon={faCopy}
694
- class="w-4 h-4 [color:var(--color-secondary)!important] group-hover:[color:color-mix(in_srgb,var(--color-secondary)_80%,black)!important] group-focus:[color:color-mix(in_srgb,currentColor_70%,transparent)!important] transition-colors"
724
+ <ContentCopy
725
+ class="w-4 h-4 [color:var(--ui-color-secondary)!important] group-hover:[color:color-mix(in_srgb,var(--ui-color-secondary)_80%,black)!important] group-focus:[color:color-mix(in_srgb,currentColor_70%,transparent)!important] transition-colors"
695
726
  />
696
727
  </div>
697
728
  {/if}
@@ -699,7 +730,7 @@
699
730
  <!-- Custom scrollbar thumb -->
700
731
  {#if showScrollbar()}
701
732
  <div
702
- class="absolute right-1 [background-color:var(--color-secondary)] rounded cursor-pointer hover:[background-color:color-mix(in_srgb,var(--color-secondary)_90%,black)] transition-colors z-10"
733
+ class="absolute right-1 [background-color:var(--ui-color-secondary)] rounded cursor-pointer hover:[background-color:color-mix(in_srgb,var(--ui-color-secondary)_90%,black)] transition-colors z-10"
703
734
  style="top: {thumbTop + 4}px; height: {thumbHeight}px; width: 6px;"
704
735
  onmousedown={handleThumbMouseDown}
705
736
  role="scrollbar"
@@ -709,7 +740,7 @@
709
740
  ? Math.round(
710
741
  (textareaElement.scrollTop /
711
742
  (textareaElement.scrollHeight - textareaElement.clientHeight)) *
712
- 100
743
+ 100,
713
744
  )
714
745
  : 0}
715
746
  aria-valuemin="0"
@@ -0,0 +1,38 @@
1
+ import type { HTMLTextareaAttributes } 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
+ type TextAreaCopy = "none" | "selection" | "button";
6
+ interface Props extends Omit<HTMLTextareaAttributes, "oncopy"> {
7
+ value?: string;
8
+ lines?: string[];
9
+ class?: string;
10
+ textareaClass?: string;
11
+ validate?: (value: string) => boolean | string;
12
+ valid?: boolean;
13
+ touched?: boolean;
14
+ element?: HTMLTextAreaElement;
15
+ minRows?: number;
16
+ maxRows?: number;
17
+ lineHeightRem?: number;
18
+ size?: Responsive<Size>;
19
+ /** Colour axis — tints the border + placeholder with the SAME calc as Input (token @ 30% at rest,
20
+ * 55% on hover/focus, mixed toward transparent). No `secondary`/`-strong` special case. */
21
+ variant?: Variant;
22
+ /** Drop the visible frame while keeping the 1px box + focus outline (border → transparent). */
23
+ borderless?: boolean;
24
+ required?: boolean;
25
+ /** How the text can be copied — one exclusive mode:
26
+ * `'selection'` (default) — copy via native text selection only, no button;
27
+ * `'button'` — also show the copy-to-clipboard button;
28
+ * `'none'` — copying disabled: no button and the text is not selectable.
29
+ * Orthogonal to `readonly`/`disabled` (all other behaviour unchanged). */
30
+ copy?: TextAreaCopy;
31
+ /** Fires after the copy button successfully writes to the clipboard, with the copied text.
32
+ * Lets a consumer confirm the copy (e.g. `oncopy={() => toast.success('Copied to clipboard')}`).
33
+ * Not called for native selection-copy, only the `copy="button"` action. */
34
+ oncopy?: (text: string) => void;
35
+ }
36
+ declare const TextArea: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched" | "lines">;
37
+ type TextArea = ReturnType<typeof TextArea>;
38
+ export default TextArea;
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import Select from "./Select.svelte";
3
+ import type { Size } from "../types/sizes.js";
4
+ import { responsiveClasses, type Responsive } from "../types/responsive.js";
5
+ import type { Variant } from "../types/variants.js";
6
+
7
+ // Shared axes, forwarded to the underlying Select (and the label text tracks size).
8
+ let { size = "md", variant }: { size?: Responsive<Size>; variant?: Variant } = $props();
9
+ const labelText: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
10
+ const labelClass = $derived(responsiveClasses(size, labelText));
11
+
12
+ const options = [
13
+ { label: "Auto (System)", value: "auto" },
14
+ { label: "Light", value: "light" },
15
+ { label: "Dark", value: "dark" },
16
+ ];
17
+
18
+ const themeClasses = {
19
+ light: "theme-light",
20
+ dark: "theme-dark",
21
+ };
22
+
23
+ let selectedTheme = $state("auto");
24
+
25
+ // Load saved theme on mount
26
+ $effect(() => {
27
+ if (typeof window !== "undefined") {
28
+ const savedTheme = localStorage.getItem("svelte-ui-theme");
29
+ if (savedTheme && options.some((opt) => opt.value === savedTheme)) {
30
+ selectedTheme = savedTheme;
31
+ }
32
+ }
33
+ });
34
+
35
+ // Apply theme to <html> (documentElement) — NOT <body>.
36
+ // The theme tokens (incl. --ui-color-hover/active) are defined at :root, and app.html's pre-hydration
37
+ // script forces the class onto <html>. If we applied it to <body> instead, <html> would stay on the
38
+ // system @media(prefers-color-scheme) path, so tokens that reference other tokens (--ui-color-hover =
39
+ // color-mix(var(--ui-color-text)…)) would resolve against the WRONG theme at :root and inherit down
40
+ // stale (e.g. a white hover-tint on a light page → invisible hover). Keep the class on :root.
41
+ function applyTheme(themeValue: string) {
42
+ const body = document.body;
43
+ const html = document.documentElement;
44
+
45
+ // Remove all existing theme classes from both body and html (clear any stray body class too)
46
+ Object.values(themeClasses).forEach((className) => {
47
+ body.classList.remove(className);
48
+ html.classList.remove(className);
49
+ });
50
+
51
+ // Add the selected theme class to <html>
52
+ const themeClass = themeClasses[themeValue as keyof typeof themeClasses];
53
+ if (themeClass) {
54
+ html.classList.add(themeClass);
55
+ }
56
+
57
+ // Save theme to localStorage
58
+ if (typeof window !== "undefined") {
59
+ localStorage.setItem("svelte-ui-theme", themeValue);
60
+ }
61
+ }
62
+
63
+ // Apply theme when selection changes
64
+ $effect(() => {
65
+ applyTheme(selectedTheme);
66
+ });
67
+ </script>
68
+
69
+ <div class="flex items-center gap-2">
70
+ <label for="theme-selector" class="{labelClass} font-medium text-[var(--ui-color-text)]"
71
+ >Theme:</label
72
+ >
73
+ <Select
74
+ bind:value={selectedTheme}
75
+ {options}
76
+ {size}
77
+ {variant}
78
+ placeholder="Select theme..."
79
+ class="w-40"
80
+ />
81
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Size } from "../types/sizes.js";
2
+ import { type Responsive } from "../types/responsive.js";
3
+ import type { Variant } from "../types/variants.js";
4
+ type $$ComponentProps = {
5
+ size?: Responsive<Size>;
6
+ variant?: Variant;
7
+ };
8
+ declare const ThemeSelector: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type ThemeSelector = ReturnType<typeof ThemeSelector>;
10
+ export default ThemeSelector;