ui-svelte 0.1.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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/charts/ArcChart.svelte +320 -0
  4. package/dist/charts/ArcChart.svelte.d.ts +26 -0
  5. package/dist/charts/AreaChart.svelte +495 -0
  6. package/dist/charts/AreaChart.svelte.d.ts +32 -0
  7. package/dist/charts/BarChart.svelte +504 -0
  8. package/dist/charts/BarChart.svelte.d.ts +38 -0
  9. package/dist/charts/Candlestick.svelte +527 -0
  10. package/dist/charts/Candlestick.svelte.d.ts +38 -0
  11. package/dist/charts/LineChart.svelte +365 -0
  12. package/dist/charts/LineChart.svelte.d.ts +36 -0
  13. package/dist/charts/PieChart.svelte +311 -0
  14. package/dist/charts/PieChart.svelte.d.ts +28 -0
  15. package/dist/charts/css/arc-chart.css +237 -0
  16. package/dist/charts/css/area-chart.css +289 -0
  17. package/dist/charts/css/bar-chart.css +167 -0
  18. package/dist/charts/css/candlestick.css +197 -0
  19. package/dist/charts/css/line-chart.css +202 -0
  20. package/dist/charts/css/pie-chart.css +199 -0
  21. package/dist/control/Audio.svelte +212 -0
  22. package/dist/control/Audio.svelte.d.ts +8 -0
  23. package/dist/control/Button.svelte +116 -0
  24. package/dist/control/Button.svelte.d.ts +22 -0
  25. package/dist/control/IconButton.svelte +104 -0
  26. package/dist/control/IconButton.svelte.d.ts +17 -0
  27. package/dist/control/Record.svelte +430 -0
  28. package/dist/control/Record.svelte.d.ts +11 -0
  29. package/dist/control/ToggleTheme.svelte +21 -0
  30. package/dist/control/ToggleTheme.svelte.d.ts +8 -0
  31. package/dist/control/Video.svelte +222 -0
  32. package/dist/control/Video.svelte.d.ts +10 -0
  33. package/dist/control/css/btn.css +206 -0
  34. package/dist/control/css/media.css +78 -0
  35. package/dist/control/css/video.css +58 -0
  36. package/dist/css/animations.css +27 -0
  37. package/dist/css/base.css +192 -0
  38. package/dist/css/utilities.css +136 -0
  39. package/dist/display/Accordion.svelte +98 -0
  40. package/dist/display/Accordion.svelte.d.ts +20 -0
  41. package/dist/display/Alert.svelte +65 -0
  42. package/dist/display/Alert.svelte.d.ts +15 -0
  43. package/dist/display/Avatar.svelte +80 -0
  44. package/dist/display/Avatar.svelte.d.ts +13 -0
  45. package/dist/display/Badge.svelte +46 -0
  46. package/dist/display/Badge.svelte.d.ts +11 -0
  47. package/dist/display/Card.svelte +94 -0
  48. package/dist/display/Card.svelte.d.ts +21 -0
  49. package/dist/display/Carousel.svelte +359 -0
  50. package/dist/display/Carousel.svelte.d.ts +25 -0
  51. package/dist/display/ChatBox.svelte +249 -0
  52. package/dist/display/ChatBox.svelte.d.ts +18 -0
  53. package/dist/display/Chip.svelte +67 -0
  54. package/dist/display/Chip.svelte.d.ts +17 -0
  55. package/dist/display/Code.svelte +56 -0
  56. package/dist/display/Code.svelte.d.ts +9 -0
  57. package/dist/display/Collapsible.svelte +71 -0
  58. package/dist/display/Collapsible.svelte.d.ts +15 -0
  59. package/dist/display/Divider.svelte +32 -0
  60. package/dist/display/Divider.svelte.d.ts +10 -0
  61. package/dist/display/Empty.svelte +462 -0
  62. package/dist/display/Empty.svelte.d.ts +11 -0
  63. package/dist/display/Icon.svelte +20 -0
  64. package/dist/display/Icon.svelte.d.ts +11 -0
  65. package/dist/display/Item.svelte +119 -0
  66. package/dist/display/Item.svelte.d.ts +24 -0
  67. package/dist/display/Loading.svelte +8 -0
  68. package/dist/display/Loading.svelte.d.ts +26 -0
  69. package/dist/display/Marquee.svelte +164 -0
  70. package/dist/display/Marquee.svelte.d.ts +21 -0
  71. package/dist/display/Section.svelte +63 -0
  72. package/dist/display/Section.svelte.d.ts +16 -0
  73. package/dist/display/Table.svelte +407 -0
  74. package/dist/display/Table.svelte.d.ts +32 -0
  75. package/dist/display/TypeWriter.svelte +23 -0
  76. package/dist/display/TypeWriter.svelte.d.ts +11 -0
  77. package/dist/display/User.svelte +0 -0
  78. package/dist/display/User.svelte.d.ts +26 -0
  79. package/dist/display/css/accordion.css +98 -0
  80. package/dist/display/css/alert.css +51 -0
  81. package/dist/display/css/avatar.css +158 -0
  82. package/dist/display/css/badge.css +47 -0
  83. package/dist/display/css/card.css +231 -0
  84. package/dist/display/css/carousel.css +156 -0
  85. package/dist/display/css/chat-box.css +188 -0
  86. package/dist/display/css/chip.css +91 -0
  87. package/dist/display/css/code.css +19 -0
  88. package/dist/display/css/collapsible.css +86 -0
  89. package/dist/display/css/divider.css +54 -0
  90. package/dist/display/css/empty.css +8 -0
  91. package/dist/display/css/item.css +149 -0
  92. package/dist/display/css/listbox.css +24 -0
  93. package/dist/display/css/marquee.css +138 -0
  94. package/dist/display/css/section.css +85 -0
  95. package/dist/display/css/table.css +361 -0
  96. package/dist/form/Checkbox.svelte +45 -0
  97. package/dist/form/Checkbox.svelte.d.ts +13 -0
  98. package/dist/form/ComboBox.svelte +448 -0
  99. package/dist/form/ComboBox.svelte.d.ts +29 -0
  100. package/dist/form/CsvField.svelte +389 -0
  101. package/dist/form/CsvField.svelte.d.ts +21 -0
  102. package/dist/form/DateField.svelte +292 -0
  103. package/dist/form/DateField.svelte.d.ts +18 -0
  104. package/dist/form/Dropzone.svelte +196 -0
  105. package/dist/form/Dropzone.svelte.d.ts +30 -0
  106. package/dist/form/ImageCropper.svelte +254 -0
  107. package/dist/form/ImageCropper.svelte.d.ts +14 -0
  108. package/dist/form/PasswordField.svelte +170 -0
  109. package/dist/form/PasswordField.svelte.d.ts +28 -0
  110. package/dist/form/PhoneField.svelte +485 -0
  111. package/dist/form/PhoneField.svelte.d.ts +25 -0
  112. package/dist/form/PinField.svelte +139 -0
  113. package/dist/form/PinField.svelte.d.ts +17 -0
  114. package/dist/form/RadioGroup.svelte +70 -0
  115. package/dist/form/RadioGroup.svelte.d.ts +19 -0
  116. package/dist/form/Select.svelte +350 -0
  117. package/dist/form/Select.svelte.d.ts +26 -0
  118. package/dist/form/Slider.svelte +60 -0
  119. package/dist/form/Slider.svelte.d.ts +15 -0
  120. package/dist/form/TextField.svelte +154 -0
  121. package/dist/form/TextField.svelte.d.ts +31 -0
  122. package/dist/form/Textarea.svelte +137 -0
  123. package/dist/form/Textarea.svelte.d.ts +27 -0
  124. package/dist/form/Toggle.svelte +45 -0
  125. package/dist/form/Toggle.svelte.d.ts +13 -0
  126. package/dist/form/css/checkbox.css +46 -0
  127. package/dist/form/css/combo-box.css +69 -0
  128. package/dist/form/css/control.css +177 -0
  129. package/dist/form/css/csv-field.css +0 -0
  130. package/dist/form/css/date.css +56 -0
  131. package/dist/form/css/dropzone.css +133 -0
  132. package/dist/form/css/field.css +17 -0
  133. package/dist/form/css/image-cropper.css +155 -0
  134. package/dist/form/css/password.css +35 -0
  135. package/dist/form/css/radio-group.css +57 -0
  136. package/dist/form/css/select.css +18 -0
  137. package/dist/form/css/slider.css +80 -0
  138. package/dist/form/css/textarea.css +130 -0
  139. package/dist/form/css/toggle.css +27 -0
  140. package/dist/form/js/countries.d.ts +13 -0
  141. package/dist/form/js/countries.js +307 -0
  142. package/dist/form/js/phone-examples.d.ts +248 -0
  143. package/dist/form/js/phone-examples.js +247 -0
  144. package/dist/hooks/use-auth.svelte.d.ts +11 -0
  145. package/dist/hooks/use-auth.svelte.js +59 -0
  146. package/dist/hooks/use-chat.svelte.d.ts +40 -0
  147. package/dist/hooks/use-chat.svelte.js +265 -0
  148. package/dist/hooks/use-clipboard.svelte.d.ts +9 -0
  149. package/dist/hooks/use-clipboard.svelte.js +52 -0
  150. package/dist/hooks/use-fetch.svelte.d.ts +11 -0
  151. package/dist/hooks/use-fetch.svelte.js +38 -0
  152. package/dist/hooks/use-form.svelte.d.ts +31 -0
  153. package/dist/hooks/use-form.svelte.js +110 -0
  154. package/dist/hooks/use-localstorage.svelte.d.ts +3 -0
  155. package/dist/hooks/use-localstorage.svelte.js +26 -0
  156. package/dist/hooks/use-scroll.svelte.d.ts +6 -0
  157. package/dist/hooks/use-scroll.svelte.js +34 -0
  158. package/dist/hooks/use-search.svelte.d.ts +49 -0
  159. package/dist/hooks/use-search.svelte.js +229 -0
  160. package/dist/hooks/use-table.svelte.d.ts +85 -0
  161. package/dist/hooks/use-table.svelte.js +362 -0
  162. package/dist/hooks/use-websocket.svelte.d.ts +18 -0
  163. package/dist/hooks/use-websocket.svelte.js +79 -0
  164. package/dist/icons/index.d.ts +132 -0
  165. package/dist/icons/index.js +132 -0
  166. package/dist/index.css +115 -0
  167. package/dist/index.d.ts +76 -0
  168. package/dist/index.js +76 -0
  169. package/dist/layout/AppBar.svelte +94 -0
  170. package/dist/layout/AppBar.svelte.d.ts +17 -0
  171. package/dist/layout/Footer.svelte +94 -0
  172. package/dist/layout/Footer.svelte.d.ts +17 -0
  173. package/dist/layout/FooterLinks.svelte +28 -0
  174. package/dist/layout/FooterLinks.svelte.d.ts +11 -0
  175. package/dist/layout/Provider.svelte +52 -0
  176. package/dist/layout/Provider.svelte.d.ts +10 -0
  177. package/dist/layout/Scaffold.svelte +46 -0
  178. package/dist/layout/Scaffold.svelte.d.ts +15 -0
  179. package/dist/layout/Sidebar.svelte +40 -0
  180. package/dist/layout/Sidebar.svelte.d.ts +13 -0
  181. package/dist/layout/css/app-bar.css +35 -0
  182. package/dist/layout/css/bottom-bar.css +12 -0
  183. package/dist/layout/css/footer-links.css +17 -0
  184. package/dist/layout/css/footer.css +35 -0
  185. package/dist/layout/css/scaffold.css +15 -0
  186. package/dist/layout/css/sidebar.css +17 -0
  187. package/dist/navigation/BottomNav.svelte +0 -0
  188. package/dist/navigation/BottomNav.svelte.d.ts +26 -0
  189. package/dist/navigation/NavMenu.svelte +254 -0
  190. package/dist/navigation/SideNav.svelte +249 -0
  191. package/dist/navigation/Tabs.svelte +79 -0
  192. package/dist/navigation/Tabs.svelte.d.ts +19 -0
  193. package/dist/navigation/css/bottom-nav.css +0 -0
  194. package/dist/navigation/css/nav-menu.css +168 -0
  195. package/dist/navigation/css/side-nav.css +244 -0
  196. package/dist/navigation/css/tabs.css +118 -0
  197. package/dist/overlay/AlertDialog.svelte +0 -0
  198. package/dist/overlay/AlertDialog.svelte.d.ts +26 -0
  199. package/dist/overlay/Command.svelte +0 -0
  200. package/dist/overlay/Command.svelte.d.ts +26 -0
  201. package/dist/overlay/Drawer.svelte +129 -0
  202. package/dist/overlay/Drawer.svelte.d.ts +20 -0
  203. package/dist/overlay/Dropdown.svelte +140 -0
  204. package/dist/overlay/Modal.svelte +102 -0
  205. package/dist/overlay/Modal.svelte.d.ts +19 -0
  206. package/dist/overlay/PopoverStack.svelte +0 -0
  207. package/dist/overlay/PopoverStack.svelte.d.ts +26 -0
  208. package/dist/overlay/Toast.svelte +83 -0
  209. package/dist/overlay/Toast.svelte.d.ts +9 -0
  210. package/dist/overlay/Tooltip.svelte +140 -0
  211. package/dist/overlay/Tooltip.svelte.d.ts +12 -0
  212. package/dist/overlay/css/drawer.css +75 -0
  213. package/dist/overlay/css/dropdown.css +24 -0
  214. package/dist/overlay/css/hovercard.css +11 -0
  215. package/dist/overlay/css/modal.css +51 -0
  216. package/dist/overlay/css/toast.css +80 -0
  217. package/dist/overlay/css/tooltip.css +89 -0
  218. package/dist/stores/i18n.svelte.d.ts +16 -0
  219. package/dist/stores/i18n.svelte.js +137 -0
  220. package/dist/stores/theme.svelte.d.ts +5 -0
  221. package/dist/stores/theme.svelte.js +55 -0
  222. package/dist/stores/toast.svelte.d.ts +19 -0
  223. package/dist/stores/toast.svelte.js +38 -0
  224. package/dist/types.d.ts +75 -0
  225. package/dist/types.js +1 -0
  226. package/dist/utils/charts.d.ts +27 -0
  227. package/dist/utils/charts.js +140 -0
  228. package/dist/utils/class-names.d.ts +1 -0
  229. package/dist/utils/class-names.js +3 -0
  230. package/dist/utils/click-outside.d.ts +3 -0
  231. package/dist/utils/click-outside.js +9 -0
  232. package/dist/utils/popover.d.ts +3 -0
  233. package/dist/utils/popover.js +17 -0
  234. package/dist/utils/ulid.d.ts +1 -0
  235. package/dist/utils/ulid.js +22 -0
  236. package/dist/utils/validate-schema.d.ts +2 -0
  237. package/dist/utils/validate-schema.js +97 -0
  238. package/package.json +69 -0
@@ -0,0 +1,196 @@
1
+ <script lang="ts">
2
+ import type { IconData } from '../display/Icon.svelte';
3
+ import { Attach24RegularIcon, Dismiss24RegularIcon } from '../icons/index.js';
4
+ import { Icon } from '../index.js';
5
+ import { cn } from '../utils/class-names.js';
6
+
7
+ type FileWithUrl = {
8
+ file: File;
9
+ name: string;
10
+ size: number;
11
+ url: Promise<string>;
12
+ uploadedAt: number;
13
+ };
14
+
15
+ type Props = {
16
+ files?: FileWithUrl[];
17
+ accept?: string;
18
+ multiple?: boolean;
19
+ maxSize?: number;
20
+ class?: string;
21
+ controlClass?: string;
22
+ icon?: IconData;
23
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
24
+ size?: 'sm' | 'md' | 'lg';
25
+ name?: string;
26
+ label?: string;
27
+ helpText?: string;
28
+ errorText?: string;
29
+ placeholder?: string;
30
+ disabled?: boolean;
31
+ onchange?: (files: FileWithUrl[]) => void;
32
+ isSolid?: boolean;
33
+ };
34
+
35
+ let {
36
+ files = $bindable([]),
37
+ accept,
38
+ multiple = true,
39
+ maxSize,
40
+ class: className,
41
+ controlClass,
42
+ icon,
43
+ variant = 'outlined',
44
+ size = 'md',
45
+ name,
46
+ label,
47
+ helpText,
48
+ errorText,
49
+ placeholder = 'Drag & drop files here or click to select',
50
+ disabled = false,
51
+ onchange,
52
+ isSolid
53
+ }: Props = $props();
54
+
55
+ const uid = $props.id();
56
+
57
+ let isActive = $state(false);
58
+ let isDragging = $state(false);
59
+
60
+ const variantClasses = {
61
+ primary: 'is-primary',
62
+ secondary: 'is-secondary',
63
+ muted: 'is-muted',
64
+ outlined: 'is-outlined',
65
+ line: 'is-line'
66
+ };
67
+
68
+ const sizeClasses = {
69
+ sm: 'is-sm',
70
+ md: 'is-md',
71
+ lg: 'is-lg'
72
+ };
73
+
74
+ const handleFiles = (fileList: FileList | null) => {
75
+ if (!fileList || disabled) return;
76
+
77
+ const newFiles: FileWithUrl[] = Array.from(fileList).map((file) => ({
78
+ file,
79
+ name: file.name,
80
+ size: file.size,
81
+ url: Promise.resolve(URL.createObjectURL(file)),
82
+ uploadedAt: Date.now()
83
+ }));
84
+
85
+ files = multiple ? [...files, ...newFiles] : newFiles;
86
+ onchange?.(files);
87
+ };
88
+
89
+ const drop = (e: DragEvent) => {
90
+ e.preventDefault();
91
+ isDragging = false;
92
+ handleFiles(e.dataTransfer?.files || null);
93
+ };
94
+
95
+ const removeFile = async (index: number) => {
96
+ const fileToRemove = files[index];
97
+ const url = await fileToRemove.url;
98
+ URL.revokeObjectURL(url);
99
+ files = [...files.slice(0, index), ...files.slice(index + 1)];
100
+ onchange?.(files);
101
+ };
102
+
103
+ const displaySize = (bytes: number): string => {
104
+ if (bytes === 0) return '0 Bytes';
105
+ const k = 1024;
106
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
107
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
108
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
109
+ };
110
+ </script>
111
+
112
+ <div class={cn('field', className)}>
113
+ {#if label}
114
+ <span class="field-label">{label}</span>
115
+ {/if}
116
+
117
+ <label
118
+ for={`${uid}-${name}`}
119
+ class={cn(
120
+ 'dropzone-input',
121
+ variantClasses[variant],
122
+ sizeClasses[size],
123
+ isSolid && 'is-solid',
124
+ (isActive || isDragging) && 'is-active',
125
+ disabled && 'is-disabled',
126
+ controlClass
127
+ )}
128
+ class:is-error={errorText}
129
+ ondragover={(e) => {
130
+ e.preventDefault();
131
+ if (!disabled) isDragging = true;
132
+ }}
133
+ ondragleave={() => (isDragging = false)}
134
+ ondrop={drop}
135
+ onmouseenter={() => !disabled && (isActive = true)}
136
+ onmouseleave={() => (isActive = false)}
137
+ >
138
+ <input
139
+ id={`${uid}-${name}`}
140
+ type="file"
141
+ hidden
142
+ {accept}
143
+ {multiple}
144
+ {disabled}
145
+ {name}
146
+ onchange={(e) => handleFiles((e.target as HTMLInputElement).files)}
147
+ />
148
+
149
+ <div class="dropzone-content">
150
+ {#if icon}
151
+ <Icon {icon} class="dropzone-icon" />
152
+ {/if}
153
+ <span class="dropzone-placeholder">{placeholder}</span>
154
+ </div>
155
+ </label>
156
+
157
+ {#if files.length > 0}
158
+ <div class="dropzone-files">
159
+ {#each files as fileItem, i (fileItem.name + fileItem.uploadedAt)}
160
+ <div class="dropzone-file">
161
+ <div class="dropzone-file-preview">
162
+ {#await fileItem.url then src}
163
+ {#if fileItem.file.type.startsWith('image/')}
164
+ <img {src} alt={fileItem.name} class="dropzone-file-image" />
165
+ {:else}
166
+ <div class="dropzone-file-icon">
167
+ <Icon icon={Attach24RegularIcon} />
168
+ </div>
169
+ {/if}
170
+ {/await}
171
+ </div>
172
+
173
+ <div class="dropzone-file-info">
174
+ <span class="dropzone-file-name">{fileItem.name}</span>
175
+ <span class="dropzone-file-size">{displaySize(fileItem.size)}</span>
176
+ </div>
177
+
178
+ <button
179
+ type="button"
180
+ class="dropzone-file-remove"
181
+ onclick={() => removeFile(i)}
182
+ aria-label="Eliminar archivo"
183
+ >
184
+ <Icon icon={Dismiss24RegularIcon} />
185
+ </button>
186
+ </div>
187
+ {/each}
188
+ </div>
189
+ {/if}
190
+
191
+ {#if errorText || helpText}
192
+ <div class={cn('field-help', errorText && 'is-danger')}>
193
+ {errorText || helpText}
194
+ </div>
195
+ {/if}
196
+ </div>
@@ -0,0 +1,30 @@
1
+ import type { IconData } from '../display/Icon.svelte';
2
+ type FileWithUrl = {
3
+ file: File;
4
+ name: string;
5
+ size: number;
6
+ url: Promise<string>;
7
+ uploadedAt: number;
8
+ };
9
+ type Props = {
10
+ files?: FileWithUrl[];
11
+ accept?: string;
12
+ multiple?: boolean;
13
+ maxSize?: number;
14
+ class?: string;
15
+ controlClass?: string;
16
+ icon?: IconData;
17
+ variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
18
+ size?: 'sm' | 'md' | 'lg';
19
+ name?: string;
20
+ label?: string;
21
+ helpText?: string;
22
+ errorText?: string;
23
+ placeholder?: string;
24
+ disabled?: boolean;
25
+ onchange?: (files: FileWithUrl[]) => void;
26
+ isSolid?: boolean;
27
+ };
28
+ declare const Dropzone: import("svelte").Component<Props, {}, "files">;
29
+ type Dropzone = ReturnType<typeof Dropzone>;
30
+ export default Dropzone;
@@ -0,0 +1,254 @@
1
+ <script lang="ts">
2
+ import { Icon } from '../index.js';
3
+ import { cn } from '../utils/class-names.js';
4
+
5
+ type Props = {
6
+ src: string;
7
+ alt?: string;
8
+ aspectRatio?: number;
9
+ minWidth?: number;
10
+ minHeight?: number;
11
+ maxWidth?: number;
12
+ maxHeight?: number;
13
+ onCrop?: (croppedImage: string) => void;
14
+ class?: string;
15
+ };
16
+
17
+ const {
18
+ src,
19
+ alt = 'Image to crop',
20
+ aspectRatio,
21
+ minWidth = 50,
22
+ minHeight = 50,
23
+ maxWidth,
24
+ maxHeight,
25
+ onCrop,
26
+ class: className
27
+ }: Props = $props();
28
+
29
+ let canvas: HTMLCanvasElement;
30
+ let image: HTMLImageElement;
31
+ let containerDiv: HTMLDivElement;
32
+
33
+ let isDragging = $state(false);
34
+ let isResizing = $state(false);
35
+ let resizeHandle = $state<string>('');
36
+
37
+ let cropX = $state(0);
38
+ let cropY = $state(0);
39
+ let cropWidth = $state(200);
40
+ let cropHeight = $state(200);
41
+
42
+ let startX = $state(0);
43
+ let startY = $state(0);
44
+ let startCropX = $state(0);
45
+ let startCropY = $state(0);
46
+ let startCropWidth = $state(0);
47
+ let startCropHeight = $state(0);
48
+
49
+ let imageLoaded = $state(false);
50
+ let imageWidth = $state(0);
51
+ let imageHeight = $state(0);
52
+ let scale = $state(1);
53
+
54
+ function handleImageLoad() {
55
+ if (!image || !containerDiv) return;
56
+
57
+ const containerWidth = containerDiv.clientWidth;
58
+ const containerHeight = containerDiv.clientHeight;
59
+
60
+ const naturalWidth = image.naturalWidth;
61
+ const naturalHeight = image.naturalHeight;
62
+
63
+ const scaleX = containerWidth / naturalWidth;
64
+ const scaleY = containerHeight / naturalHeight;
65
+ scale = Math.min(scaleX, scaleY, 1);
66
+
67
+ imageWidth = naturalWidth * scale;
68
+ imageHeight = naturalHeight * scale;
69
+
70
+ const initialSize = Math.min(imageWidth, imageHeight) * 0.6;
71
+ cropWidth = aspectRatio ? initialSize : initialSize;
72
+ cropHeight = aspectRatio ? initialSize / aspectRatio : initialSize;
73
+ cropX = (imageWidth - cropWidth) / 2;
74
+ cropY = (imageHeight - cropHeight) / 2;
75
+
76
+ imageLoaded = true;
77
+ }
78
+
79
+ function handleMouseDown(e: MouseEvent, handle?: string) {
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+
83
+ if (handle) {
84
+ isResizing = true;
85
+ resizeHandle = handle;
86
+ } else {
87
+ isDragging = true;
88
+ }
89
+
90
+ startX = e.clientX;
91
+ startY = e.clientY;
92
+ startCropX = cropX;
93
+ startCropY = cropY;
94
+ startCropWidth = cropWidth;
95
+ startCropHeight = cropHeight;
96
+
97
+ document.addEventListener('mousemove', handleMouseMove);
98
+ document.addEventListener('mouseup', handleMouseUp);
99
+ }
100
+
101
+ function handleMouseMove(e: MouseEvent) {
102
+ const deltaX = e.clientX - startX;
103
+ const deltaY = e.clientY - startY;
104
+
105
+ if (isDragging) {
106
+ cropX = Math.max(0, Math.min(imageWidth - cropWidth, startCropX + deltaX));
107
+ cropY = Math.max(0, Math.min(imageHeight - cropHeight, startCropY + deltaY));
108
+ } else if (isResizing) {
109
+ let newWidth = startCropWidth;
110
+ let newHeight = startCropHeight;
111
+ let newX = startCropX;
112
+ let newY = startCropY;
113
+
114
+ if (resizeHandle.includes('e')) {
115
+ newWidth = Math.max(minWidth, Math.min(imageWidth - startCropX, startCropWidth + deltaX));
116
+ }
117
+ if (resizeHandle.includes('w')) {
118
+ newWidth = Math.max(minWidth, startCropWidth - deltaX);
119
+ newX = startCropX + (startCropWidth - newWidth);
120
+ }
121
+ if (resizeHandle.includes('s')) {
122
+ newHeight = Math.max(
123
+ minHeight,
124
+ Math.min(imageHeight - startCropY, startCropHeight + deltaY)
125
+ );
126
+ }
127
+ if (resizeHandle.includes('n')) {
128
+ newHeight = Math.max(minHeight, startCropHeight - deltaY);
129
+ newY = startCropY + (startCropHeight - newHeight);
130
+ }
131
+
132
+ if (aspectRatio) {
133
+ if (resizeHandle.includes('e') || resizeHandle.includes('w')) {
134
+ newHeight = newWidth / aspectRatio;
135
+ } else {
136
+ newWidth = newHeight * aspectRatio;
137
+ }
138
+ }
139
+
140
+ if (maxWidth) newWidth = Math.min(newWidth, maxWidth);
141
+ if (maxHeight) newHeight = Math.min(newHeight, maxHeight);
142
+
143
+ cropWidth = newWidth;
144
+ cropHeight = newHeight;
145
+ cropX = Math.max(0, Math.min(imageWidth - cropWidth, newX));
146
+ cropY = Math.max(0, Math.min(imageHeight - cropHeight, newY));
147
+ }
148
+ }
149
+
150
+ function handleMouseUp() {
151
+ isDragging = false;
152
+ isResizing = false;
153
+ resizeHandle = '';
154
+ document.removeEventListener('mousemove', handleMouseMove);
155
+ document.removeEventListener('mouseup', handleMouseUp);
156
+ }
157
+
158
+ function handleCrop() {
159
+ if (!canvas || !image) return;
160
+
161
+ const ctx = canvas.getContext('2d');
162
+ if (!ctx) return;
163
+
164
+ const scaleX = image.naturalWidth / imageWidth;
165
+ const scaleY = image.naturalHeight / imageHeight;
166
+
167
+ canvas.width = cropWidth * scaleX;
168
+ canvas.height = cropHeight * scaleY;
169
+
170
+ ctx.drawImage(
171
+ image,
172
+ cropX * scaleX,
173
+ cropY * scaleY,
174
+ cropWidth * scaleX,
175
+ cropHeight * scaleY,
176
+ 0,
177
+ 0,
178
+ canvas.width,
179
+ canvas.height
180
+ );
181
+
182
+ const croppedImageUrl = canvas.toDataURL('image/png');
183
+ onCrop?.(croppedImageUrl);
184
+ }
185
+
186
+ function handleReset() {
187
+ if (!imageLoaded) return;
188
+ const initialSize = Math.min(imageWidth, imageHeight) * 0.6;
189
+ cropWidth = aspectRatio ? initialSize : initialSize;
190
+ cropHeight = aspectRatio ? initialSize / aspectRatio : initialSize;
191
+ cropX = (imageWidth - cropWidth) / 2;
192
+ cropY = (imageHeight - cropHeight) / 2;
193
+ }
194
+ </script>
195
+
196
+ <div class={cn('image-cropper', className)}>
197
+ <div class="image-cropper-container" bind:this={containerDiv}>
198
+ <img
199
+ bind:this={image}
200
+ {src}
201
+ {alt}
202
+ class="image-cropper-image"
203
+ onload={handleImageLoad}
204
+ style="width: {imageWidth}px; height: {imageHeight}px;"
205
+ />
206
+
207
+ {#if imageLoaded}
208
+ <div class="image-cropper-overlay">
209
+ <div class="overlay-top" style="height: {cropY}px;"></div>
210
+ <div class="overlay-middle" style="top: {cropY}px; height: {cropHeight}px;">
211
+ <div class="overlay-left" style="width: {cropX}px;"></div>
212
+ <div
213
+ class="overlay-right"
214
+ style="left: {cropX + cropWidth}px; width: {imageWidth - cropX - cropWidth}px;"
215
+ ></div>
216
+ </div>
217
+ <div
218
+ class="overlay-bottom"
219
+ style="top: {cropY + cropHeight}px; height: {imageHeight - cropY - cropHeight}px;"
220
+ ></div>
221
+
222
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
223
+ <div
224
+ class="crop-area"
225
+ style="left: {cropX}px; top: {cropY}px; width: {cropWidth}px; height: {cropHeight}px;"
226
+ onmousedown={(e) => handleMouseDown(e)}
227
+ >
228
+ <div class="crop-grid">
229
+ <div class="grid-line grid-line-v" style="left: 33.33%;"></div>
230
+ <div class="grid-line grid-line-v" style="left: 66.66%;"></div>
231
+ <div class="grid-line grid-line-h" style="top: 33.33%;"></div>
232
+ <div class="grid-line grid-line-h" style="top: 66.66%;"></div>
233
+ </div>
234
+
235
+ <div class="resize-handle handle-nw" onmousedown={(e) => handleMouseDown(e, 'nw')}></div>
236
+ <div class="resize-handle handle-n" onmousedown={(e) => handleMouseDown(e, 'n')}></div>
237
+ <div class="resize-handle handle-ne" onmousedown={(e) => handleMouseDown(e, 'ne')}></div>
238
+ <div class="resize-handle handle-e" onmousedown={(e) => handleMouseDown(e, 'e')}></div>
239
+ <div class="resize-handle handle-se" onmousedown={(e) => handleMouseDown(e, 'se')}></div>
240
+ <div class="resize-handle handle-s" onmousedown={(e) => handleMouseDown(e, 's')}></div>
241
+ <div class="resize-handle handle-sw" onmousedown={(e) => handleMouseDown(e, 'sw')}></div>
242
+ <div class="resize-handle handle-w" onmousedown={(e) => handleMouseDown(e, 'w')}></div>
243
+ </div>
244
+ </div>
245
+ {/if}
246
+ </div>
247
+
248
+ <div class="image-cropper-controls">
249
+ <button class="btn is-md is-outlined" onclick={handleReset}> refresh icon </button>
250
+ <button class="btn is-md is-primary is-solid" onclick={handleCrop}> crop icon </button>
251
+ </div>
252
+
253
+ <canvas bind:this={canvas} class="image-cropper-canvas"></canvas>
254
+ </div>
@@ -0,0 +1,14 @@
1
+ type Props = {
2
+ src: string;
3
+ alt?: string;
4
+ aspectRatio?: number;
5
+ minWidth?: number;
6
+ minHeight?: number;
7
+ maxWidth?: number;
8
+ maxHeight?: number;
9
+ onCrop?: (croppedImage: string) => void;
10
+ class?: string;
11
+ };
12
+ declare const ImageCropper: import("svelte").Component<Props, {}, "">;
13
+ type ImageCropper = ReturnType<typeof ImageCropper>;
14
+ export default ImageCropper;
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ import Button from '../control/Button.svelte';
3
+ import { Icon } from '../index.js';
4
+ import { cn } from '../utils/class-names.js';
5
+ import type { HTMLInputAttributes } from 'svelte/elements';
6
+
7
+ type Props = {
8
+ value?: string;
9
+ defaultValue?: string;
10
+ placeholder?: string;
11
+ type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
12
+ class?: string;
13
+ inputClass?: string;
14
+ onchange?: (value: string | number | undefined) => void;
15
+ oninput?: (value: string | number | undefined) => void;
16
+ variant?: 'solid' | 'outlined' | 'soft' | 'line';
17
+ color?: 'primary' | 'secondary' | 'muted';
18
+ inputSize?: 'small' | 'medium' | 'large';
19
+ name?: string;
20
+ label?: string;
21
+ labelOutside?: boolean;
22
+ labelActive?: boolean;
23
+ helpText?: string;
24
+ errorText?: string;
25
+ labels?: {
26
+ weak?: string;
27
+ medium?: string;
28
+ strong?: string;
29
+ };
30
+ } & HTMLInputAttributes;
31
+
32
+ let {
33
+ value = $bindable(),
34
+ defaultValue,
35
+ placeholder,
36
+ type = 'text',
37
+ class: className,
38
+ inputClass,
39
+ onchange,
40
+ oninput,
41
+ variant = 'soft',
42
+ color = 'primary',
43
+ inputSize = 'medium',
44
+ name,
45
+ label,
46
+ labelOutside,
47
+ labelActive,
48
+ helpText,
49
+ errorText,
50
+ labels = {
51
+ weak: 'Weak',
52
+ medium: 'Medium',
53
+ strong: 'Strong'
54
+ },
55
+ ...rest
56
+ }: Props = $props();
57
+
58
+ const variants = {
59
+ solid: 'field-solid',
60
+ outlined: 'field-outlined',
61
+ soft: 'field-soft',
62
+ line: 'field-line'
63
+ };
64
+ const colors = {
65
+ primary: 'field-primary',
66
+ secondary: 'field-secondary',
67
+ muted: 'field-muted'
68
+ };
69
+
70
+ const sizes = {
71
+ small: 'field-small',
72
+ medium: 'field-medium',
73
+ large: 'field-large'
74
+ };
75
+
76
+ const uid = $props.id();
77
+
78
+ let show = $state(false);
79
+ let isFocused = $state(false);
80
+ let strength = $derived(calculateStrength(value));
81
+
82
+ function isBarActive(barIndex: number, score: number): boolean {
83
+ const threshold = (barIndex + 1) * 1.5;
84
+ return score >= threshold;
85
+ }
86
+
87
+ function calculateStrength(password: string): {
88
+ score: number;
89
+ label: string;
90
+ color: string;
91
+ } {
92
+ if (!password) return { score: 0, label: '', color: 'bg-gray-300' };
93
+
94
+ let score = 0;
95
+
96
+ if (password.length >= 8) score += 1;
97
+ if (password.length >= 12) score += 1;
98
+ if (/[a-z]/.test(password)) score += 1;
99
+ if (/[A-Z]/.test(password)) score += 1;
100
+ if (/\d/.test(password)) score += 1;
101
+ if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score += 1;
102
+ if (score <= 2) return { score, label: labels.weak || 'Weak', color: 'bg-red-500' };
103
+ if (score <= 4)
104
+ return {
105
+ score,
106
+ label: labels.medium || 'Medium',
107
+ color: 'bg-yellow-500'
108
+ };
109
+ return {
110
+ score,
111
+ label: labels.strong || 'Strong',
112
+ color: 'bg-green-500'
113
+ };
114
+ }
115
+ </script>
116
+
117
+ <div class={cn('field', className)}>
118
+ {#if labelOutside && label}
119
+ <label for={`${uid}-{name}`} class="label">{label}</label>
120
+ {/if}
121
+ <div
122
+ class={cn('field-control', variants[variant], colors[color], sizes[inputSize])}
123
+ class:is-error={errorText}
124
+ >
125
+ {#if !labelOutside && label}
126
+ <label class:is-active={isFocused || value} for={`${uid}-{name}`} class="label-inside"
127
+ >{label}</label
128
+ >
129
+ {/if}
130
+ <input
131
+ type={show ? 'text' : 'password'}
132
+ bind:value
133
+ id={`${uid}-{name}`}
134
+ class={cn('field-input', inputClass)}
135
+ placeholder={isFocused ? placeholder : ''}
136
+ {name}
137
+ {defaultValue}
138
+ onchange={(e) => onchange?.((e.target as HTMLInputElement).value)}
139
+ oninput={(e) => oninput?.((e.target as HTMLInputElement).value)}
140
+ onfocusin={() => {
141
+ if (!labelActive) isFocused = true;
142
+ }}
143
+ onfocusout={() => {
144
+ if (!labelActive) isFocused = false;
145
+ }}
146
+ {...rest}
147
+ />
148
+ <Button class={cn('field-eye')} onclick={() => (show = !show)}>eye icon</Button>
149
+ </div>
150
+ <div class={cn('field-strength', className)}>
151
+ <div class="field-strength-bars">
152
+ {#each Array(4) as _, i}
153
+ <div
154
+ class="field-strength-bar-item"
155
+ class:active={isBarActive(i, strength.score)}
156
+ class:weak={strength.score <= 2 && isBarActive(i, strength.score)}
157
+ class:medium={strength.score > 2 && strength.score <= 4 && isBarActive(i, strength.score)}
158
+ class:strong={strength.score > 4 && isBarActive(i, strength.score)}
159
+ ></div>
160
+ {/each}
161
+ </div>
162
+ </div>
163
+ {#if errorText || helpText || strength.label}
164
+ <div
165
+ class={cn('field-help', strength.label && 'field-strength-label', errorText && 'is-error')}
166
+ >
167
+ {strength.label ? strength.label : errorText ? errorText : helpText}
168
+ </div>
169
+ {/if}
170
+ </div>
@@ -0,0 +1,28 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ type Props = {
3
+ value?: string;
4
+ defaultValue?: string;
5
+ placeholder?: string;
6
+ type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
7
+ class?: string;
8
+ inputClass?: string;
9
+ onchange?: (value: string | number | undefined) => void;
10
+ oninput?: (value: string | number | undefined) => void;
11
+ variant?: 'solid' | 'outlined' | 'soft' | 'line';
12
+ color?: 'primary' | 'secondary' | 'muted';
13
+ inputSize?: 'small' | 'medium' | 'large';
14
+ name?: string;
15
+ label?: string;
16
+ labelOutside?: boolean;
17
+ labelActive?: boolean;
18
+ helpText?: string;
19
+ errorText?: string;
20
+ labels?: {
21
+ weak?: string;
22
+ medium?: string;
23
+ strong?: string;
24
+ };
25
+ } & HTMLInputAttributes;
26
+ declare const PasswordField: import("svelte").Component<Props, {}, "value">;
27
+ type PasswordField = ReturnType<typeof PasswordField>;
28
+ export default PasswordField;