@spaethtech/svelte-ui 0.1.10

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 (116) hide show
  1. package/README.md +81 -0
  2. package/dist/components/Badge/Badge.svelte +80 -0
  3. package/dist/components/Badge/Badge.svelte.d.ts +14 -0
  4. package/dist/components/Badge/index.d.ts +1 -0
  5. package/dist/components/Badge/index.js +1 -0
  6. package/dist/components/Button/Button.svelte +172 -0
  7. package/dist/components/Button/Button.svelte.d.ts +32 -0
  8. package/dist/components/Button/index.d.ts +1 -0
  9. package/dist/components/Button/index.js +1 -0
  10. package/dist/components/Dialog/Dialog.svelte +101 -0
  11. package/dist/components/Dialog/Dialog.svelte.d.ts +18 -0
  12. package/dist/components/Dialog/index.d.ts +1 -0
  13. package/dist/components/Dialog/index.js +1 -0
  14. package/dist/components/ElementManager/ElementManager.svelte +397 -0
  15. package/dist/components/ElementManager/ElementManager.svelte.d.ts +43 -0
  16. package/dist/components/ElementManager/index.d.ts +1 -0
  17. package/dist/components/ElementManager/index.js +1 -0
  18. package/dist/components/EmailInput/EmailInput.svelte +47 -0
  19. package/dist/components/EmailInput/EmailInput.svelte.d.ts +14 -0
  20. package/dist/components/EmailInput/index.d.ts +1 -0
  21. package/dist/components/EmailInput/index.js +1 -0
  22. package/dist/components/Icon/Icon.svelte +74 -0
  23. package/dist/components/Icon/Icon.svelte.d.ts +13 -0
  24. package/dist/components/Icon/index.d.ts +1 -0
  25. package/dist/components/Icon/index.js +1 -0
  26. package/dist/components/Input/Input.svelte +268 -0
  27. package/dist/components/Input/Input.svelte.d.ts +18 -0
  28. package/dist/components/Input/index.d.ts +1 -0
  29. package/dist/components/Input/index.js +1 -0
  30. package/dist/components/List/List.svelte +580 -0
  31. package/dist/components/List/List.svelte.d.ts +38 -0
  32. package/dist/components/List/index.d.ts +1 -0
  33. package/dist/components/List/index.js +1 -0
  34. package/dist/components/ListItem/ListItem.svelte +175 -0
  35. package/dist/components/ListItem/ListItem.svelte.d.ts +24 -0
  36. package/dist/components/ListItem/index.d.ts +2 -0
  37. package/dist/components/ListItem/index.js +2 -0
  38. package/dist/components/ListView/ListView.svelte +463 -0
  39. package/dist/components/ListView/ListView.svelte.d.ts +37 -0
  40. package/dist/components/ListView/index.d.ts +2 -0
  41. package/dist/components/ListView/index.js +2 -0
  42. package/dist/components/NodeGraph/BaseNode.d.ts +57 -0
  43. package/dist/components/NodeGraph/BaseNode.js +30 -0
  44. package/dist/components/NodeGraph/Edge.svelte +60 -0
  45. package/dist/components/NodeGraph/Edge.svelte.d.ts +16 -0
  46. package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +82 -0
  47. package/dist/components/NodeGraph/ExpressionEvaluator.js +152 -0
  48. package/dist/components/NodeGraph/Node.svelte +100 -0
  49. package/dist/components/NodeGraph/Node.svelte.d.ts +10 -0
  50. package/dist/components/NodeGraph/NodeGraph.svelte +52 -0
  51. package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +14 -0
  52. package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +80 -0
  53. package/dist/components/NodeGraph/NodeInstance.svelte.js +241 -0
  54. package/dist/components/NodeGraph/NodePort.svelte +75 -0
  55. package/dist/components/NodeGraph/NodePort.svelte.d.ts +11 -0
  56. package/dist/components/NodeGraph/decorators.d.ts +81 -0
  57. package/dist/components/NodeGraph/decorators.js +148 -0
  58. package/dist/components/NodeGraph/index.d.ts +9 -0
  59. package/dist/components/NodeGraph/index.js +9 -0
  60. package/dist/components/NodeGraph/types.d.ts +94 -0
  61. package/dist/components/NodeGraph/types.js +33 -0
  62. package/dist/components/NotesEditor/NotesEditor.svelte +203 -0
  63. package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +9 -0
  64. package/dist/components/NotesEditor/index.d.ts +1 -0
  65. package/dist/components/NotesEditor/index.js +1 -0
  66. package/dist/components/NumberInput/NumberInput.svelte +353 -0
  67. package/dist/components/NumberInput/NumberInput.svelte.d.ts +26 -0
  68. package/dist/components/NumberInput/index.d.ts +1 -0
  69. package/dist/components/NumberInput/index.js +1 -0
  70. package/dist/components/PasswordInput/PasswordInput.svelte +41 -0
  71. package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +13 -0
  72. package/dist/components/PasswordInput/index.d.ts +1 -0
  73. package/dist/components/PasswordInput/index.js +1 -0
  74. package/dist/components/Popup/index.d.ts +1 -0
  75. package/dist/components/Popup/index.js +1 -0
  76. package/dist/components/Rating/Rating.svelte +77 -0
  77. package/dist/components/Rating/Rating.svelte.d.ts +7 -0
  78. package/dist/components/Rating/index.d.ts +1 -0
  79. package/dist/components/Rating/index.js +1 -0
  80. package/dist/components/ScrollView/ScrollView.svelte +285 -0
  81. package/dist/components/ScrollView/ScrollView.svelte.d.ts +19 -0
  82. package/dist/components/ScrollView/index.d.ts +1 -0
  83. package/dist/components/ScrollView/index.js +1 -0
  84. package/dist/components/SearchInput/SearchInput.svelte +50 -0
  85. package/dist/components/SearchInput/SearchInput.svelte.d.ts +15 -0
  86. package/dist/components/SearchInput/index.d.ts +1 -0
  87. package/dist/components/SearchInput/index.js +1 -0
  88. package/dist/components/Select/Select.svelte +278 -0
  89. package/dist/components/Select/Select.svelte.d.ts +35 -0
  90. package/dist/components/Select/index.d.ts +1 -0
  91. package/dist/components/Select/index.js +1 -0
  92. package/dist/components/TextArea/TextArea.svelte +734 -0
  93. package/dist/components/TextArea/TextArea.svelte.d.ts +19 -0
  94. package/dist/components/TextArea/index.d.ts +1 -0
  95. package/dist/components/TextArea/index.js +1 -0
  96. package/dist/components/ThemeSelector/ThemeSelector.svelte +64 -0
  97. package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +3 -0
  98. package/dist/components/ThemeSelector/index.d.ts +1 -0
  99. package/dist/components/ThemeSelector/index.js +1 -0
  100. package/dist/components/TooltipHandler/TooltipHandler.svelte +593 -0
  101. package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +10 -0
  102. package/dist/components/TooltipHandler/index.d.ts +1 -0
  103. package/dist/components/TooltipHandler/index.js +1 -0
  104. package/dist/index.d.ts +24 -0
  105. package/dist/index.js +26 -0
  106. package/dist/styles/sizes.d.ts +78 -0
  107. package/dist/styles/sizes.js +120 -0
  108. package/dist/styles/variants.d.ts +106 -0
  109. package/dist/styles/variants.js +194 -0
  110. package/dist/types/ManagerTypes.d.ts +42 -0
  111. package/dist/types/ManagerTypes.js +1 -0
  112. package/dist/types/sizes.d.ts +5 -0
  113. package/dist/types/sizes.js +1 -0
  114. package/dist/types/variants.d.ts +1 -0
  115. package/dist/types/variants.js +1 -0
  116. package/package.json +65 -0
@@ -0,0 +1,397 @@
1
+ <!--
2
+ /**
3
+ * ElementManager - Utility component for automatic element discovery and management
4
+ *
5
+ * Separates element discovery from component logic for better architecture.
6
+ * Used as a foundation for building manager components like TooltipHandler, ModalManager, etc.
7
+ *
8
+ * @example Single Selector (backwards compatible)
9
+ * ```svelte
10
+ * <script>
11
+ * let tooltipElements = $state(new Map());
12
+ * </script>
13
+ *
14
+ * <ElementManager
15
+ * selector="[data-tooltip], [data-tooltip-text]"
16
+ * bind:elements={tooltipElements}
17
+ * />
18
+ *
19
+ * <TooltipHandler elements={tooltipElements} />
20
+ * <slot />
21
+ * ```
22
+ *
23
+ * @example Multiple Selector Groups (new)
24
+ * ```svelte
25
+ * <script>
26
+ * let elementGroups = $state({});
27
+ * </script>
28
+ *
29
+ * <ElementManager
30
+ * selectors={{
31
+ * viewportTooltips: "[data-tooltip]:not([data-contained])",
32
+ * containerTooltips: "[data-tooltip][data-contained]",
33
+ * modals: "[data-modal]"
34
+ * }}
35
+ * bind:elements={elementGroups}
36
+ * >
37
+ * <TooltipHandler bind:elements={elementGroups.viewportTooltips} />
38
+ * <TooltipHandler bind:elements={elementGroups.containerTooltips} container=".modal" />
39
+ * <ModalManager bind:elements={elementGroups.modals} />
40
+ * <slot />
41
+ * </ElementManager>
42
+ * ```
43
+ *
44
+ * @example With Event Callbacks
45
+ * ```svelte
46
+ * <ElementManager
47
+ * selectors={{ modals: "[data-modal]" }}
48
+ * bind:elements={elementGroups}
49
+ * onElementAdded={(element, data, group) => {
50
+ * console.log(`New ${group} trigger:`, element);
51
+ * }}
52
+ * onElementRemoved={(element, data, group) => {
53
+ * // Cleanup any modal state
54
+ * data.cleanup?.();
55
+ * }}
56
+ * />
57
+ * ```
58
+ *
59
+ * @example Custom Observer Options
60
+ * ```svelte
61
+ * <ElementManager
62
+ * selectors={{ editable: "[contenteditable]" }}
63
+ * observerOptions={{
64
+ * childList: true,
65
+ * characterData: true,
66
+ * subtree: true
67
+ * }}
68
+ * bind:elements={elementGroups}
69
+ * />
70
+ * ```
71
+ */
72
+ -->
73
+ <script lang="ts">
74
+ import { onMount, untrack } from 'svelte';
75
+ import type { Snippet } from 'svelte';
76
+
77
+ /**
78
+ * Data structure for each discovered element
79
+ */
80
+ interface ElementData {
81
+ /** The HTML element that was discovered */
82
+ element: HTMLElement;
83
+ /** Optional cleanup function called when element is removed */
84
+ cleanup?: () => void;
85
+ /** Allow extension by consumer components with additional properties */
86
+ [key: string]: any;
87
+ }
88
+
89
+ /**
90
+ * ElementManager component props
91
+ *
92
+ * @example Basic usage
93
+ * ```svelte
94
+ * <ElementManager
95
+ * selectors={{
96
+ * tooltips: "[data-tooltip]",
97
+ * modals: "[data-modal]"
98
+ * }}
99
+ * bind:elements={elementGroups}
100
+ * />
101
+ * ```
102
+ */
103
+ interface Props {
104
+ /** Multiple CSS selectors organized by group name */
105
+ selectors: Record<string, string>;
106
+ /** Bindable Record of Maps for discovered elements */
107
+ elements?: Record<string, Map<HTMLElement, ElementData>>;
108
+ /** Optional child content to wrap (uses class="contents" for transparent rendering) */
109
+ children?: Snippet;
110
+ /** Callback fired when a new element is discovered */
111
+ onElementAdded?: (element: HTMLElement, data: ElementData, group: string) => void;
112
+ /** Callback fired when an element is removed or no longer matches selector */
113
+ onElementRemoved?: (element: HTMLElement, data: ElementData, group: string) => void;
114
+ /** Custom MutationObserver configuration (defaults to childList, subtree, attributes) */
115
+ observerOptions?: MutationObserverInit;
116
+ }
117
+
118
+ let {
119
+ selectors,
120
+ elements = $bindable(),
121
+ children,
122
+ onElementAdded,
123
+ onElementRemoved,
124
+ observerOptions
125
+ }: Props = $props();
126
+
127
+ // ===== DEVELOPMENT WARNINGS =====
128
+
129
+ if (import.meta.env.DEV) {
130
+ // Validate prop usage
131
+ if (!selectors || Object.keys(selectors).length === 0) {
132
+ console.warn('ElementManager: No selectors provided or selectors object is empty. No elements will be discovered.');
133
+ }
134
+ }
135
+
136
+ // ===== COMPONENT STATE =====
137
+ let containerElement: HTMLElement;
138
+ let observer: MutationObserver | null = null;
139
+ let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
140
+ let initialized = false;
141
+
142
+ // Initialize selectors as empty object if not provided (for development warnings)
143
+ let currentSelectors = $derived(() => selectors || {});
144
+
145
+ // Initialize elements (runs before other effects)
146
+ $effect.pre(() => {
147
+ if (!elements && !initialized) {
148
+ initialized = true;
149
+ untrack(() => {
150
+ // Initialize with empty Maps for each group
151
+ const groups: Record<string, Map<HTMLElement, ElementData>> = {};
152
+ for (const group in currentSelectors()) {
153
+ groups[group] = new Map();
154
+ }
155
+ elements = groups;
156
+ });
157
+ }
158
+ });
159
+
160
+ // ===== ELEMENT DISCOVERY =====
161
+
162
+ /**
163
+ * Discovers elements matching the current selectors and updates the elements Map(s).
164
+ * Performs incremental updates to avoid full re-discovery on every change.
165
+ */
166
+ function discoverElements() {
167
+ if (!containerElement || !containerElement.isConnected) return;
168
+
169
+ const selectorGroups = currentSelectors();
170
+
171
+ // Track all found elements by group
172
+ const foundByGroup: Record<string, Set<HTMLElement>> = {};
173
+
174
+ // Discover elements for each selector group
175
+ for (const [group, selectorString] of Object.entries(selectorGroups)) {
176
+ foundByGroup[group] = new Set();
177
+
178
+ try {
179
+ const foundElements = containerElement.querySelectorAll(selectorString);
180
+
181
+ // Warn in development if no elements found
182
+ if (import.meta.env.DEV && foundElements.length === 0) {
183
+ console.warn(`ElementManager: No elements matched selector "${selectorString}" for group "${group}"`);
184
+ }
185
+
186
+ foundElements.forEach((el) => {
187
+ if (el instanceof HTMLElement) {
188
+ foundByGroup[group].add(el);
189
+ }
190
+ });
191
+ } catch (error) {
192
+ console.warn(`ElementManager: Invalid selector "${selectorString}" for group "${group}":`,
193
+ error instanceof Error ? error.message : error);
194
+ }
195
+ }
196
+
197
+ // Update elements for each group
198
+ const groupedElements = elements as Record<string, Map<HTMLElement, ElementData>>;
199
+ const newGroupedElements: Record<string, Map<HTMLElement, ElementData>> = {};
200
+
201
+ for (const [group, foundSet] of Object.entries(foundByGroup)) {
202
+ const currentMap = groupedElements[group] || new Map();
203
+ const newMap = new Map(currentMap);
204
+
205
+ // Remove elements that no longer match
206
+ for (const [element, data] of currentMap) {
207
+ if (!foundSet.has(element)) {
208
+ // Call cleanup function if provided
209
+ if (data.cleanup) {
210
+ try {
211
+ data.cleanup();
212
+ } catch (error) {
213
+ console.warn('ElementManager: Error in element cleanup:', error);
214
+ }
215
+ }
216
+
217
+ // Call removal callback
218
+ if (onElementRemoved) {
219
+ try {
220
+ onElementRemoved(element, data, group);
221
+ } catch (error) {
222
+ console.warn('ElementManager: Error in onElementRemoved callback:', error);
223
+ }
224
+ }
225
+
226
+ newMap.delete(element);
227
+ }
228
+ }
229
+
230
+ // Add new elements
231
+ foundSet.forEach((element) => {
232
+ if (!currentMap.has(element)) {
233
+ const elementData: ElementData = { element };
234
+ newMap.set(element, elementData);
235
+
236
+ // Call addition callback
237
+ if (onElementAdded) {
238
+ try {
239
+ onElementAdded(element, elementData, group);
240
+ } catch (error) {
241
+ console.warn('ElementManager: Error in onElementAdded callback:', error);
242
+ }
243
+ }
244
+ }
245
+ });
246
+
247
+ newGroupedElements[group] = newMap;
248
+ }
249
+
250
+ // Handle removed groups
251
+ for (const group in groupedElements) {
252
+ if (!(group in newGroupedElements)) {
253
+ // Clean up all elements in removed group
254
+ const currentMap = groupedElements[group];
255
+ for (const [element, data] of currentMap) {
256
+ if (data.cleanup) {
257
+ try {
258
+ data.cleanup();
259
+ } catch (error) {
260
+ console.warn('ElementManager: Error in element cleanup for removed group:', error);
261
+ }
262
+ }
263
+ if (onElementRemoved) {
264
+ try {
265
+ onElementRemoved(element, data, group);
266
+ } catch (error) {
267
+ console.warn('ElementManager: Error in onElementRemoved callback for removed group:', error);
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ elements = newGroupedElements;
275
+ }
276
+
277
+ /**
278
+ * Debounced version of discoverElements to prevent excessive updates during rapid DOM changes.
279
+ * Uses 16ms delay (~60fps) for smooth performance.
280
+ */
281
+ function debouncedDiscoverElements() {
282
+ if (debounceTimeout) {
283
+ clearTimeout(debounceTimeout);
284
+ }
285
+
286
+ debounceTimeout = setTimeout(() => {
287
+ discoverElements();
288
+ debounceTimeout = null;
289
+ }, 16); // ~60fps for smooth updates
290
+ }
291
+
292
+ // ===== MUTATION OBSERVER MANAGEMENT =====
293
+
294
+ /**
295
+ * Sets up MutationObserver to watch for DOM changes and trigger element re-discovery.
296
+ * Includes error handling and recovery mechanisms.
297
+ */
298
+ function setupObserver() {
299
+ if (observer || !containerElement || !containerElement.isConnected) return;
300
+
301
+ observer = new MutationObserver(() => {
302
+ try {
303
+ debouncedDiscoverElements();
304
+ } catch (error) {
305
+ console.warn('ElementManager: Error in MutationObserver callback:', error);
306
+ // Attempt to recover by re-running discovery without debouncing
307
+ try {
308
+ discoverElements();
309
+ } catch (recoveryError) {
310
+ console.warn('ElementManager: Failed to recover from MutationObserver error:', recoveryError);
311
+ }
312
+ }
313
+ });
314
+
315
+ try {
316
+ // Use custom options or simple defaults
317
+ const options: MutationObserverInit = observerOptions || {
318
+ childList: true,
319
+ subtree: true,
320
+ attributes: true
321
+ // No attributeFilter - watch all attribute changes for simplicity
322
+ };
323
+
324
+ observer.observe(containerElement, options);
325
+ } catch (error) {
326
+ console.warn('ElementManager: Failed to set up MutationObserver:', error);
327
+ observer = null;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Cleanup function called on component destruction.
333
+ * Disconnects observer, clears timeouts, and runs element cleanup callbacks.
334
+ */
335
+ function cleanup() {
336
+ if (observer) {
337
+ observer.disconnect();
338
+ observer = null;
339
+ }
340
+
341
+ if (debounceTimeout) {
342
+ clearTimeout(debounceTimeout);
343
+ debounceTimeout = null;
344
+ }
345
+
346
+ // Clean up all remaining elements
347
+ if (elements) {
348
+ const groupedElements = elements as Record<string, Map<HTMLElement, ElementData>>;
349
+ for (const group in groupedElements) {
350
+ const currentMap = groupedElements[group];
351
+ for (const [, data] of currentMap) {
352
+ if (data.cleanup) {
353
+ try {
354
+ data.cleanup();
355
+ } catch (error) {
356
+ console.warn('ElementManager: Error in element cleanup during component destruction:', error);
357
+ }
358
+ }
359
+ }
360
+ currentMap.clear();
361
+ }
362
+ }
363
+ }
364
+
365
+ // React to selector changes and handle initial setup
366
+ $effect(() => {
367
+ // Ensure selectors are tracked as dependencies
368
+ currentSelectors();
369
+
370
+ // Only proceed if we have a container and are initialized
371
+ if (containerElement && initialized) {
372
+ // Clean up existing observer
373
+ if (observer) {
374
+ observer.disconnect();
375
+ observer = null;
376
+ }
377
+
378
+ // Re-discover with new selectors (untrack to prevent reactivity loop)
379
+ untrack(() => {
380
+ discoverElements();
381
+ });
382
+
383
+ // Set up observer with new selectors
384
+ setupObserver();
385
+ }
386
+ });
387
+
388
+ onMount(() => {
389
+ return cleanup;
390
+ });
391
+ </script>
392
+
393
+ <div bind:this={containerElement} class="contents">
394
+ {#if children}
395
+ {@render children()}
396
+ {/if}
397
+ </div>
@@ -0,0 +1,43 @@
1
+ import type { Snippet } from 'svelte';
2
+ /**
3
+ * Data structure for each discovered element
4
+ */
5
+ interface ElementData {
6
+ /** The HTML element that was discovered */
7
+ element: HTMLElement;
8
+ /** Optional cleanup function called when element is removed */
9
+ cleanup?: () => void;
10
+ /** Allow extension by consumer components with additional properties */
11
+ [key: string]: any;
12
+ }
13
+ /**
14
+ * ElementManager component props
15
+ *
16
+ * @example Basic usage
17
+ * ```svelte
18
+ * <ElementManager
19
+ * selectors={{
20
+ * tooltips: "[data-tooltip]",
21
+ * modals: "[data-modal]"
22
+ * }}
23
+ * bind:elements={elementGroups}
24
+ * />
25
+ * ```
26
+ */
27
+ interface Props {
28
+ /** Multiple CSS selectors organized by group name */
29
+ selectors: Record<string, string>;
30
+ /** Bindable Record of Maps for discovered elements */
31
+ elements?: Record<string, Map<HTMLElement, ElementData>>;
32
+ /** Optional child content to wrap (uses class="contents" for transparent rendering) */
33
+ children?: Snippet;
34
+ /** Callback fired when a new element is discovered */
35
+ onElementAdded?: (element: HTMLElement, data: ElementData, group: string) => void;
36
+ /** Callback fired when an element is removed or no longer matches selector */
37
+ onElementRemoved?: (element: HTMLElement, data: ElementData, group: string) => void;
38
+ /** Custom MutationObserver configuration (defaults to childList, subtree, attributes) */
39
+ observerOptions?: MutationObserverInit;
40
+ }
41
+ declare const ElementManager: import("svelte").Component<Props, {}, "elements">;
42
+ type ElementManager = ReturnType<typeof ElementManager>;
43
+ export default ElementManager;
@@ -0,0 +1 @@
1
+ export { default } from './ElementManager.svelte';
@@ -0,0 +1 @@
1
+ export { default } from './ElementManager.svelte';
@@ -0,0 +1,47 @@
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';
5
+
6
+ interface Props extends Omit<HTMLInputAttributes, 'type'> {
7
+ value: string;
8
+ class?: string;
9
+ inputClass?: string;
10
+ validate?: (value: string) => boolean | string;
11
+ valid?: boolean;
12
+ touched?: boolean;
13
+ element?: HTMLInputElement;
14
+ required?: boolean;
15
+ }
16
+
17
+ let {
18
+ value = $bindable(),
19
+ valid = $bindable(true),
20
+ touched = $bindable(false),
21
+ element = $bindable(),
22
+ validate,
23
+ required = false,
24
+ ...restProps
25
+ }: Props = $props();
26
+
27
+ // Default email validation
28
+ 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';
31
+ return true;
32
+ };
33
+
34
+ // Use custom validator if provided, otherwise use default
35
+ const emailValidator = validate || defaultEmailValidator;
36
+ </script>
37
+
38
+ <Input
39
+ bind:value
40
+ bind:valid
41
+ bind:touched
42
+ bind:element
43
+ type="email"
44
+ icon={faEnvelope}
45
+ validate={emailValidator}
46
+ {...restProps}
47
+ />
@@ -0,0 +1,14 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ interface Props extends Omit<HTMLInputAttributes, 'type'> {
3
+ value: string;
4
+ class?: string;
5
+ inputClass?: string;
6
+ validate?: (value: string) => boolean | string;
7
+ valid?: boolean;
8
+ touched?: boolean;
9
+ element?: HTMLInputElement;
10
+ required?: boolean;
11
+ }
12
+ declare const EmailInput: import("svelte").Component<Props, {}, "element" | "value" | "valid" | "touched">;
13
+ type EmailInput = ReturnType<typeof EmailInput>;
14
+ export default EmailInput;
@@ -0,0 +1 @@
1
+ export { default } from './EmailInput.svelte';
@@ -0,0 +1 @@
1
+ export { default } from './EmailInput.svelte';
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ import type { IconDefinition } from '@fortawesome/fontawesome-svg-core';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import type { Variant } from '../../types/variants.js';
5
+
6
+ interface Props extends HTMLAttributes<SVGElement> {
7
+ icon: IconDefinition;
8
+ class?: string;
9
+ size?: string;
10
+ color?: string;
11
+ variant?: Omit<Variant, 'ghost'>;
12
+ }
13
+
14
+ let {
15
+ icon,
16
+ class: className = '',
17
+ size,
18
+ color,
19
+ variant,
20
+ ...restProps
21
+ }: Props = $props();
22
+
23
+ const getIconSize = (size?: string) => {
24
+ if (!size) return '1rem';
25
+
26
+ // Support explicit sizes (px, rem, em, etc.)
27
+ if (size.match(/^\d+(\.\d+)?(px|rem|em|%)$/)) {
28
+ return size;
29
+ }
30
+
31
+ // Fallback to 1rem for invalid sizes
32
+ return '1rem';
33
+ };
34
+
35
+ const svgWidth = $derived(icon.icon[0]);
36
+ const svgHeight = $derived(icon.icon[1]);
37
+ const svgPath = $derived(icon.icon[4]);
38
+ const iconSize = $derived(getIconSize(size));
39
+
40
+ // Color priority: explicit color > variant color > currentColor
41
+ const fillColor = $derived(() => {
42
+ if (color) return color;
43
+ if (variant) return `var(--color-${variant})`;
44
+ return 'currentColor';
45
+ });
46
+ </script>
47
+
48
+ <svg
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ viewBox="0 0 {svgWidth} {svgHeight}"
51
+ class="svg-inline--fa {className}"
52
+ aria-hidden="true"
53
+ focusable="false"
54
+ role="img"
55
+ style:fill={fillColor()}
56
+ style:height={iconSize}
57
+ {...restProps}
58
+ >
59
+ {#if typeof svgPath === 'string'}
60
+ <path d={svgPath} />
61
+ {:else if Array.isArray(svgPath)}
62
+ {#each svgPath as path}
63
+ <path d={path} />
64
+ {/each}
65
+ {/if}
66
+ </svg>
67
+
68
+ <style>
69
+ .svg-inline--fa {
70
+ display: inline-block;
71
+ overflow: visible;
72
+ vertical-align: -0.125em;
73
+ }
74
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { IconDefinition } from '@fortawesome/fontawesome-svg-core';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { Variant } from '../../types/variants.js';
4
+ interface Props extends HTMLAttributes<SVGElement> {
5
+ icon: IconDefinition;
6
+ class?: string;
7
+ size?: string;
8
+ color?: string;
9
+ variant?: Omit<Variant, 'ghost'>;
10
+ }
11
+ declare const Icon: import("svelte").Component<Props, {}, "">;
12
+ type Icon = ReturnType<typeof Icon>;
13
+ export default Icon;
@@ -0,0 +1 @@
1
+ export { default } from './Icon.svelte';
@@ -0,0 +1 @@
1
+ export { default } from './Icon.svelte';