ui-svelte 0.2.11 → 0.2.13

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 (225) hide show
  1. package/README.md +2 -2
  2. package/dist/charts/ArcChart.svelte +9 -14
  3. package/dist/charts/ArcChart.svelte.d.ts +3 -3
  4. package/dist/charts/AreaChart.svelte +347 -118
  5. package/dist/charts/AreaChart.svelte.d.ts +33 -4
  6. package/dist/charts/BarChart.svelte +288 -66
  7. package/dist/charts/BarChart.svelte.d.ts +26 -1
  8. package/dist/charts/Candlestick.svelte +53 -50
  9. package/dist/charts/Candlestick.svelte.d.ts +8 -8
  10. package/dist/charts/LineChart.svelte +391 -91
  11. package/dist/charts/LineChart.svelte.d.ts +26 -3
  12. package/dist/charts/PieChart.svelte +333 -92
  13. package/dist/charts/PieChart.svelte.d.ts +33 -5
  14. package/dist/charts/css/arc-chart.css +3 -3
  15. package/dist/charts/css/area-chart.css +127 -29
  16. package/dist/charts/css/bar-chart.css +114 -8
  17. package/dist/charts/css/candlestick.css +2 -0
  18. package/dist/charts/css/line-chart.css +111 -13
  19. package/dist/charts/css/pie-chart.css +92 -20
  20. package/dist/control/Audio.svelte +86 -44
  21. package/dist/control/Audio.svelte.d.ts +4 -1
  22. package/dist/control/Button.svelte +18 -27
  23. package/dist/control/Button.svelte.d.ts +3 -2
  24. package/dist/control/Fab.svelte +103 -0
  25. package/dist/control/Fab.svelte.d.ts +25 -0
  26. package/dist/control/IconButton.svelte +17 -27
  27. package/dist/control/IconButton.svelte.d.ts +3 -3
  28. package/dist/control/Image.svelte +123 -0
  29. package/dist/control/Image.svelte.d.ts +13 -0
  30. package/dist/control/Record.svelte +141 -98
  31. package/dist/control/Record.svelte.d.ts +2 -1
  32. package/dist/control/ToggleGroup.svelte +22 -8
  33. package/dist/control/ToggleGroup.svelte.d.ts +2 -1
  34. package/dist/control/ToggleTheme.svelte +13 -11
  35. package/dist/control/ToggleTheme.svelte.d.ts +3 -2
  36. package/dist/control/Video.svelte +57 -29
  37. package/dist/control/Video.svelte.d.ts +1 -0
  38. package/dist/control/css/btn.css +200 -152
  39. package/dist/control/css/fab.css +84 -0
  40. package/dist/control/css/image.css +56 -0
  41. package/dist/control/css/media.css +95 -30
  42. package/dist/control/css/toggle-group.css +253 -84
  43. package/dist/control/css/video.css +1 -14
  44. package/dist/css/animations.css +5 -9
  45. package/dist/css/base.css +13 -347
  46. package/dist/css/decorations.css +561 -0
  47. package/dist/css/rich-text.css +485 -0
  48. package/dist/css/transitions.css +158 -0
  49. package/dist/css/typography.css +291 -0
  50. package/dist/css/utilities.css +0 -4
  51. package/dist/display/Accordion.svelte +28 -4
  52. package/dist/display/Accordion.svelte.d.ts +2 -1
  53. package/dist/display/Alert.svelte +32 -12
  54. package/dist/display/Alert.svelte.d.ts +2 -3
  55. package/dist/display/Avatar.svelte +23 -18
  56. package/dist/display/Avatar.svelte.d.ts +4 -1
  57. package/dist/display/AvatarGroup.svelte +20 -18
  58. package/dist/display/AvatarGroup.svelte.d.ts +6 -3
  59. package/dist/display/Badge.svelte +11 -4
  60. package/dist/display/Badge.svelte.d.ts +2 -1
  61. package/dist/display/Card.svelte +15 -14
  62. package/dist/display/Card.svelte.d.ts +2 -3
  63. package/dist/display/Carousel.svelte +130 -99
  64. package/dist/display/Carousel.svelte.d.ts +6 -4
  65. package/dist/display/ChatBox.svelte +245 -106
  66. package/dist/display/ChatBox.svelte.d.ts +32 -5
  67. package/dist/display/Chip.svelte +31 -17
  68. package/dist/display/Chip.svelte.d.ts +3 -2
  69. package/dist/display/Code.svelte +7 -4
  70. package/dist/display/Code.svelte.d.ts +1 -0
  71. package/dist/display/Collapsible.svelte +30 -4
  72. package/dist/display/Collapsible.svelte.d.ts +2 -1
  73. package/dist/display/Countdown.svelte +169 -0
  74. package/dist/display/Countdown.svelte.d.ts +21 -0
  75. package/dist/display/Empty.svelte +37 -3
  76. package/dist/display/Empty.svelte.d.ts +3 -0
  77. package/dist/display/Item.svelte +42 -11
  78. package/dist/display/Item.svelte.d.ts +4 -2
  79. package/dist/display/Map.svelte +488 -0
  80. package/dist/display/Map.svelte.d.ts +44 -0
  81. package/dist/display/Marquee.svelte +0 -2
  82. package/dist/display/Section.svelte +14 -12
  83. package/dist/display/Section.svelte.d.ts +2 -3
  84. package/dist/display/Skeleton.svelte +32 -0
  85. package/dist/display/Skeleton.svelte.d.ts +10 -0
  86. package/dist/display/Table.svelte +94 -132
  87. package/dist/display/Table.svelte.d.ts +10 -1
  88. package/dist/display/css/accordion.css +349 -52
  89. package/dist/display/css/alert.css +38 -18
  90. package/dist/display/css/avatar-group.css +38 -75
  91. package/dist/display/css/avatar.css +139 -121
  92. package/dist/display/css/badge.css +50 -27
  93. package/dist/display/css/card.css +123 -71
  94. package/dist/display/css/carousel.css +25 -5
  95. package/dist/display/css/chat-box.css +158 -26
  96. package/dist/display/css/chip.css +142 -68
  97. package/dist/display/css/code.css +2 -6
  98. package/dist/display/css/collapsible.css +349 -45
  99. package/dist/display/css/countdown.css +206 -0
  100. package/dist/display/css/divider.css +8 -6
  101. package/dist/display/css/empty.css +7 -0
  102. package/dist/display/css/item.css +330 -84
  103. package/dist/display/css/map.css +164 -0
  104. package/dist/display/css/marquee.css +0 -3
  105. package/dist/display/css/section.css +89 -65
  106. package/dist/display/css/skeleton.css +58 -0
  107. package/dist/display/css/table.css +309 -193
  108. package/dist/form/Checkbox.svelte +11 -5
  109. package/dist/form/Checkbox.svelte.d.ts +2 -1
  110. package/dist/form/ColorField.svelte +601 -0
  111. package/dist/form/ColorField.svelte.d.ts +29 -0
  112. package/dist/form/ComboBox.svelte +24 -9
  113. package/dist/form/ComboBox.svelte.d.ts +2 -2
  114. package/dist/form/CsvField.svelte +62 -136
  115. package/dist/form/CsvField.svelte.d.ts +2 -2
  116. package/dist/form/DateField.svelte +33 -15
  117. package/dist/form/DateField.svelte.d.ts +2 -1
  118. package/dist/form/DateRange.svelte +436 -0
  119. package/dist/form/DateRange.svelte.d.ts +24 -0
  120. package/dist/form/DragDrop.svelte +578 -0
  121. package/dist/form/DragDrop.svelte.d.ts +33 -0
  122. package/dist/form/Dropzone.svelte +28 -8
  123. package/dist/form/Dropzone.svelte.d.ts +2 -2
  124. package/dist/form/Editor.svelte +626 -0
  125. package/dist/form/Editor.svelte.d.ts +50 -0
  126. package/dist/form/ImageCropper.svelte +422 -61
  127. package/dist/form/ImageCropper.svelte.d.ts +15 -1
  128. package/dist/form/{PasswordStrength.svelte → PasswordField.svelte} +58 -24
  129. package/dist/form/{PasswordStrength.svelte.d.ts → PasswordField.svelte.d.ts} +6 -5
  130. package/dist/form/PhoneField.svelte +26 -14
  131. package/dist/form/PhoneField.svelte.d.ts +4 -3
  132. package/dist/form/PinField.svelte +39 -31
  133. package/dist/form/PinField.svelte.d.ts +3 -3
  134. package/dist/form/RadioGroup.svelte +9 -5
  135. package/dist/form/RadioGroup.svelte.d.ts +1 -1
  136. package/dist/form/Select.svelte +20 -19
  137. package/dist/form/Select.svelte.d.ts +2 -2
  138. package/dist/form/Slider.svelte +10 -4
  139. package/dist/form/Slider.svelte.d.ts +2 -1
  140. package/dist/form/TextField.svelte +29 -11
  141. package/dist/form/TextField.svelte.d.ts +5 -4
  142. package/dist/form/Textarea.svelte +15 -6
  143. package/dist/form/Textarea.svelte.d.ts +2 -2
  144. package/dist/form/Toggle.svelte +7 -3
  145. package/dist/form/Toggle.svelte.d.ts +1 -1
  146. package/dist/form/css/checkbox.css +18 -2
  147. package/dist/form/css/color-field.css +141 -0
  148. package/dist/form/css/control.css +193 -82
  149. package/dist/form/css/csv-field.css +221 -0
  150. package/dist/form/css/date-range.css +122 -0
  151. package/dist/form/css/date.css +24 -2
  152. package/dist/form/css/drag-drop.css +234 -0
  153. package/dist/form/css/dropzone.css +153 -34
  154. package/dist/form/css/editor.css +367 -0
  155. package/dist/form/css/field.css +4 -0
  156. package/dist/form/css/image-cropper.css +242 -20
  157. package/dist/form/css/radio-group.css +26 -1
  158. package/dist/form/css/select.css +2 -2
  159. package/dist/form/css/slider.css +37 -0
  160. package/dist/form/css/textarea.css +178 -75
  161. package/dist/form/css/toggle.css +15 -3
  162. package/dist/hooks/use-chat.svelte.js +1 -1
  163. package/dist/hooks/use-form.svelte.js +3 -3
  164. package/dist/hooks/use-search.svelte.js +0 -3
  165. package/dist/hooks/use-table.svelte.d.ts +1 -0
  166. package/dist/hooks/use-table.svelte.js +6 -0
  167. package/dist/icons/index.d.ts +34 -2
  168. package/dist/icons/index.js +36 -4
  169. package/dist/index.css +44 -49
  170. package/dist/index.d.ts +14 -4
  171. package/dist/index.js +13 -3
  172. package/dist/layout/AppBar.svelte +22 -14
  173. package/dist/layout/AppBar.svelte.d.ts +2 -1
  174. package/dist/layout/Footer.svelte +19 -11
  175. package/dist/layout/Footer.svelte.d.ts +2 -1
  176. package/dist/layout/Provider.svelte +32 -9
  177. package/dist/layout/Provider.svelte.d.ts +3 -1
  178. package/dist/layout/Sidebar.svelte +17 -8
  179. package/dist/layout/Sidebar.svelte.d.ts +2 -1
  180. package/dist/layout/css/app-bar.css +63 -66
  181. package/dist/layout/css/footer.css +62 -65
  182. package/dist/layout/css/sidebar.css +120 -59
  183. package/dist/navigation/BottomNav.svelte +51 -14
  184. package/dist/navigation/FooterGroup.svelte +1 -1
  185. package/dist/navigation/NavMenu.svelte +47 -23
  186. package/dist/navigation/NavMenu.svelte.d.ts +29 -0
  187. package/dist/navigation/Pagination.svelte +158 -0
  188. package/dist/navigation/Pagination.svelte.d.ts +18 -0
  189. package/dist/navigation/SideNav.svelte +30 -25
  190. package/dist/navigation/SideNav.svelte.d.ts +2 -3
  191. package/dist/navigation/Tabs.svelte +17 -7
  192. package/dist/navigation/Tabs.svelte.d.ts +2 -2
  193. package/dist/navigation/css/bottom-nav.css +319 -257
  194. package/dist/navigation/css/footer-group.css +1 -1
  195. package/dist/navigation/css/footer-nav.css +1 -1
  196. package/dist/navigation/css/nav-menu.css +331 -106
  197. package/dist/navigation/css/pagination.css +74 -0
  198. package/dist/navigation/css/side-nav.css +514 -75
  199. package/dist/navigation/css/tabs.css +246 -52
  200. package/dist/overlay/AlertDialog.svelte +58 -0
  201. package/dist/overlay/AlertDialog.svelte.d.ts +14 -25
  202. package/dist/overlay/Command.svelte +347 -0
  203. package/dist/overlay/Command.svelte.d.ts +33 -25
  204. package/dist/overlay/Drawer.svelte +49 -21
  205. package/dist/overlay/Drawer.svelte.d.ts +2 -2
  206. package/dist/overlay/Dropdown.svelte +3 -3
  207. package/dist/overlay/Modal.svelte +51 -16
  208. package/dist/overlay/Modal.svelte.d.ts +3 -3
  209. package/dist/overlay/Toast.svelte +41 -17
  210. package/dist/overlay/Toast.svelte.d.ts +1 -1
  211. package/dist/overlay/Tooltip.svelte +36 -27
  212. package/dist/overlay/Tooltip.svelte.d.ts +2 -2
  213. package/dist/overlay/css/command.css +68 -0
  214. package/dist/overlay/css/drawer.css +63 -24
  215. package/dist/overlay/css/dropdown.css +1 -1
  216. package/dist/overlay/css/hovercard.css +1 -1
  217. package/dist/overlay/css/modal.css +79 -27
  218. package/dist/overlay/css/toast.css +40 -24
  219. package/dist/overlay/css/tooltip.css +110 -67
  220. package/dist/stores/theme.svelte.js +44 -12
  221. package/dist/stores/toast.svelte.d.ts +4 -4
  222. package/dist/stores/toast.svelte.js +2 -2
  223. package/package.json +1 -1
  224. package/dist/utils/charts.d.ts +0 -27
  225. package/dist/utils/charts.js +0 -140
@@ -0,0 +1,578 @@
1
+ <script lang="ts" module>
2
+ export type DragDropItem = {
3
+ id: string;
4
+ [key: string]: unknown;
5
+ };
6
+
7
+ export type DragDropGroup = {
8
+ id: string;
9
+ title?: string;
10
+ items: DragDropItem[];
11
+ [key: string]: unknown;
12
+ };
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { cn } from '../utils/class-names.js';
17
+ import type { Snippet } from 'svelte';
18
+ import { onMount } from 'svelte';
19
+
20
+ type Props = {
21
+ groups?: DragDropGroup[];
22
+ items?: DragDropItem[];
23
+ class?: string;
24
+ groupClass?: string;
25
+ itemClass?: string;
26
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
27
+ variant?: 'soft' | 'solid' | 'outlined' | 'ghost';
28
+ size?: 'sm' | 'md' | 'lg';
29
+ direction?: 'horizontal' | 'vertical';
30
+ isDisabled?: boolean;
31
+ allowGroupTransfer?: boolean;
32
+ onReorder?: (items: DragDropItem[], fromGroup?: string, toGroup?: string) => void;
33
+ onGroupReorder?: (groups: DragDropGroup[]) => void;
34
+ itemSnippet?: Snippet<[DragDropItem, number]>;
35
+ groupHeaderSnippet?: Snippet<[DragDropGroup, number]>;
36
+ emptySnippet?: Snippet<[DragDropGroup?]>;
37
+ emptyText?: string;
38
+ };
39
+
40
+ let {
41
+ groups = $bindable([]),
42
+ items = $bindable([]),
43
+ class: className,
44
+ groupClass,
45
+ itemClass,
46
+ color = 'muted',
47
+ variant = 'soft',
48
+ size = 'md',
49
+ direction = 'vertical',
50
+ isDisabled = false,
51
+ allowGroupTransfer = true,
52
+ onReorder,
53
+ onGroupReorder,
54
+ itemSnippet,
55
+ groupHeaderSnippet,
56
+ emptySnippet,
57
+ emptyText = 'Drop items here'
58
+ }: Props = $props();
59
+
60
+ let draggedItem = $state<DragDropItem | null>(null);
61
+ let draggedFromGroup = $state<string | null>(null);
62
+ let draggedFromIndex = $state(-1);
63
+ let dragOverGroup = $state<string | null>(null);
64
+ let dragOverIndex = $state(-1);
65
+ let isDragging = $state(false);
66
+
67
+ let containerEl = $state<HTMLElement>();
68
+ let isTouchDragging = $state(false);
69
+ let isTouchPending = $state(false);
70
+ let touchDraggedElement = $state<HTMLElement | null>(null);
71
+ let ghostElement = $state<HTMLElement | null>(null);
72
+ let isTouchDevice = $state(false);
73
+
74
+ let touchStart = { x: 0, y: 0 };
75
+ let touchCurrent = { x: 0, y: 0 };
76
+ let longPressTimer: ReturnType<typeof setTimeout> | null = null;
77
+ const LONG_PRESS_DURATION = 400;
78
+ const SCROLL_THRESHOLD = 8;
79
+
80
+ const colorClass = {
81
+ primary: 'is-primary',
82
+ secondary: 'is-secondary',
83
+ muted: 'is-muted',
84
+ success: 'is-success',
85
+ info: 'is-info',
86
+ danger: 'is-danger',
87
+ warning: 'is-warning'
88
+ };
89
+
90
+ const variantClass = {
91
+ soft: 'is-soft',
92
+ solid: 'is-solid',
93
+ outlined: 'is-outlined',
94
+ ghost: 'is-ghost'
95
+ };
96
+
97
+ const sizeClass = {
98
+ sm: 'is-sm',
99
+ md: 'is-md',
100
+ lg: 'is-lg'
101
+ };
102
+
103
+ const isSingleMode = $derived(groups.length === 0);
104
+
105
+ function performReorder(dropIndex: number, dropGroupId?: string) {
106
+ if (!draggedItem) return;
107
+
108
+ if (isSingleMode) {
109
+ const newItems = [...items];
110
+ newItems.splice(draggedFromIndex, 1);
111
+ const adjustedIndex = dropIndex > draggedFromIndex ? dropIndex - 1 : dropIndex;
112
+ newItems.splice(adjustedIndex, 0, draggedItem);
113
+ items = newItems;
114
+ onReorder?.(newItems);
115
+ } else {
116
+ const newGroups = groups.map((g) => ({ ...g, items: [...g.items] }));
117
+
118
+ if (draggedFromGroup === dropGroupId) {
119
+ const group = newGroups.find((g) => g.id === dropGroupId);
120
+ if (group) {
121
+ group.items.splice(draggedFromIndex, 1);
122
+ const adjustedIndex = dropIndex > draggedFromIndex ? dropIndex - 1 : dropIndex;
123
+ group.items.splice(adjustedIndex, 0, draggedItem);
124
+ }
125
+ } else {
126
+ const fromGroup = newGroups.find((g) => g.id === draggedFromGroup);
127
+ const toGroup = newGroups.find((g) => g.id === dropGroupId);
128
+ if (fromGroup && toGroup) {
129
+ fromGroup.items.splice(draggedFromIndex, 1);
130
+ toGroup.items.splice(dropIndex, 0, draggedItem);
131
+ }
132
+ }
133
+
134
+ groups = newGroups;
135
+ onReorder?.(
136
+ newGroups.find((g) => g.id === dropGroupId)?.items || [],
137
+ draggedFromGroup ?? undefined,
138
+ dropGroupId
139
+ );
140
+ onGroupReorder?.(newGroups);
141
+ }
142
+ }
143
+
144
+ function performDropOnEmpty(groupId: string) {
145
+ if (!draggedItem || isSingleMode) return;
146
+
147
+ const newGroups = groups.map((g) => ({ ...g, items: [...g.items] }));
148
+ const fromGroup = newGroups.find((g) => g.id === draggedFromGroup);
149
+ const toGroup = newGroups.find((g) => g.id === groupId);
150
+
151
+ if (fromGroup && toGroup) {
152
+ fromGroup.items.splice(draggedFromIndex, 1);
153
+ toGroup.items.push(draggedItem);
154
+ }
155
+
156
+ groups = newGroups;
157
+ onReorder?.(toGroup?.items || [], draggedFromGroup ?? undefined, groupId);
158
+ onGroupReorder?.(newGroups);
159
+ }
160
+
161
+ function resetDragState() {
162
+ draggedItem = null;
163
+ draggedFromGroup = null;
164
+ draggedFromIndex = -1;
165
+ dragOverGroup = null;
166
+ dragOverIndex = -1;
167
+ isDragging = false;
168
+ isTouchDragging = false;
169
+ }
170
+
171
+ function handleDragStart(e: DragEvent, item: DragDropItem, index: number, groupId?: string) {
172
+ if (isDisabled || isTouchDevice || isTouchDragging) {
173
+ e.preventDefault();
174
+ return;
175
+ }
176
+
177
+ draggedItem = item;
178
+ draggedFromIndex = index;
179
+ draggedFromGroup = groupId ?? null;
180
+ isDragging = true;
181
+
182
+ if (e.dataTransfer) {
183
+ e.dataTransfer.effectAllowed = 'move';
184
+ e.dataTransfer.setData('text/plain', item.id);
185
+ }
186
+
187
+ const target = e.target as HTMLElement;
188
+ requestAnimationFrame(() => target.classList.add('is-dragging'));
189
+ }
190
+
191
+ function handleDragEnd(e: DragEvent) {
192
+ (e.target as HTMLElement).classList.remove('is-dragging');
193
+ resetDragState();
194
+ }
195
+
196
+ function handleDragOver(e: DragEvent, index: number, groupId?: string) {
197
+ e.preventDefault();
198
+ if (!draggedItem || isDisabled) return;
199
+ if (!allowGroupTransfer && groupId !== draggedFromGroup) return;
200
+
201
+ dragOverIndex = index;
202
+ dragOverGroup = groupId ?? null;
203
+
204
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
205
+ }
206
+
207
+ function handleDragLeave() {
208
+ dragOverIndex = -1;
209
+ }
210
+
211
+ function handleDrop(e: DragEvent, dropIndex: number, groupId?: string) {
212
+ e.preventDefault();
213
+ if (!draggedItem || isDisabled) return;
214
+
215
+ if (!allowGroupTransfer && groupId !== draggedFromGroup) {
216
+ handleDragEnd(e);
217
+ return;
218
+ }
219
+
220
+ performReorder(dropIndex, groupId);
221
+ handleDragEnd(e);
222
+ }
223
+
224
+ function handleDropOnEmpty(e: DragEvent, groupId?: string) {
225
+ e.preventDefault();
226
+ if (!draggedItem || isDisabled) return;
227
+
228
+ if (!allowGroupTransfer && groupId !== draggedFromGroup) {
229
+ handleDragEnd(e);
230
+ return;
231
+ }
232
+
233
+ if (groupId) performDropOnEmpty(groupId);
234
+ handleDragEnd(e);
235
+ }
236
+
237
+ function createGhostElement(source: HTMLElement): HTMLElement {
238
+ const ghost = source.cloneNode(true) as HTMLElement;
239
+ Object.assign(ghost.style, {
240
+ position: 'fixed',
241
+ pointerEvents: 'none',
242
+ zIndex: '9999',
243
+ opacity: '0.9',
244
+ width: `${source.offsetWidth}px`,
245
+ transform: 'scale(1.02)',
246
+ boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)'
247
+ });
248
+ ghost.classList.add('drag-drop-ghost');
249
+ document.body.appendChild(ghost);
250
+ return ghost;
251
+ }
252
+
253
+ function updateGhostPosition(x: number, y: number) {
254
+ if (ghostElement) {
255
+ ghostElement.style.left = `${x - ghostElement.offsetWidth / 2}px`;
256
+ ghostElement.style.top = `${y - ghostElement.offsetHeight / 2}px`;
257
+ }
258
+ }
259
+
260
+ function removeGhostElement() {
261
+ ghostElement?.remove();
262
+ ghostElement = null;
263
+ }
264
+
265
+ function clearTimers() {
266
+ if (longPressTimer) {
267
+ clearTimeout(longPressTimer);
268
+ longPressTimer = null;
269
+ }
270
+ }
271
+
272
+ function cancelTouchDrag() {
273
+ clearTimers();
274
+ removeGhostElement();
275
+ touchDraggedElement?.classList.remove('is-dragging');
276
+ isTouchPending = false;
277
+ resetDragState();
278
+ }
279
+
280
+ function getItemAtPoint(
281
+ x: number,
282
+ y: number
283
+ ): { element: HTMLElement; index: number; groupId?: string } | null {
284
+ if (!containerEl) return null;
285
+
286
+ const allItems = containerEl.querySelectorAll('.drag-drop-item');
287
+ for (const item of allItems) {
288
+ const rect = item.getBoundingClientRect();
289
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
290
+ const groupEl = item.closest('.drag-drop-group');
291
+ const groupId = groupEl?.getAttribute('data-group-id') || undefined;
292
+ const listEl = item.closest('.drag-drop-list');
293
+ const siblings = listEl?.querySelectorAll('.drag-drop-item') || [];
294
+ const index = Array.from(siblings).indexOf(item);
295
+ return { element: item as HTMLElement, index, groupId };
296
+ }
297
+ }
298
+
299
+ const emptyZones = containerEl.querySelectorAll('.drag-drop-empty');
300
+ for (const zone of emptyZones) {
301
+ const rect = zone.getBoundingClientRect();
302
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
303
+ const groupEl = zone.closest('.drag-drop-group');
304
+ const groupId = groupEl?.getAttribute('data-group-id') || undefined;
305
+ return { element: zone as HTMLElement, index: 0, groupId };
306
+ }
307
+ }
308
+
309
+ return null;
310
+ }
311
+
312
+ function handleTouchStart(e: TouchEvent, item: DragDropItem, index: number, groupId?: string) {
313
+ if (isDisabled) return;
314
+
315
+ const touch = e.touches[0];
316
+ touchStart = { x: touch.clientX, y: touch.clientY };
317
+ touchCurrent = { ...touchStart };
318
+
319
+ touchDraggedElement = e.currentTarget as HTMLElement;
320
+ isTouchPending = true;
321
+
322
+ longPressTimer = setTimeout(() => {
323
+ if (!isTouchPending) return;
324
+
325
+ isTouchDragging = true;
326
+ isDragging = true;
327
+ isTouchPending = false;
328
+ draggedItem = item;
329
+ draggedFromIndex = index;
330
+ draggedFromGroup = groupId ?? null;
331
+
332
+ touchDraggedElement?.classList.add('is-dragging');
333
+ ghostElement = createGhostElement(touchDraggedElement!);
334
+ updateGhostPosition(touchCurrent.x, touchCurrent.y);
335
+
336
+ navigator.vibrate?.(50);
337
+ }, LONG_PRESS_DURATION);
338
+ }
339
+
340
+ function handleTouchMove(e: TouchEvent) {
341
+ const touch = e.touches[0];
342
+ touchCurrent = { x: touch.clientX, y: touch.clientY };
343
+
344
+ const deltaX = Math.abs(touchCurrent.x - touchStart.x);
345
+ const deltaY = Math.abs(touchCurrent.y - touchStart.y);
346
+
347
+ if (!isTouchDragging && (deltaX > SCROLL_THRESHOLD || deltaY > SCROLL_THRESHOLD)) {
348
+ cancelTouchDrag();
349
+ return;
350
+ }
351
+
352
+ if (!isTouchDragging || !draggedItem) return;
353
+
354
+ e.preventDefault();
355
+ updateGhostPosition(touchCurrent.x, touchCurrent.y);
356
+
357
+ containerEl
358
+ ?.querySelectorAll('.is-touch-drag-over')
359
+ .forEach((el) => el.classList.remove('is-touch-drag-over'));
360
+
361
+ const itemAtPoint = getItemAtPoint(touchCurrent.x, touchCurrent.y);
362
+ if (itemAtPoint && itemAtPoint.element !== touchDraggedElement) {
363
+ if (!allowGroupTransfer && itemAtPoint.groupId !== draggedFromGroup) return;
364
+
365
+ dragOverIndex = itemAtPoint.index;
366
+ dragOverGroup = itemAtPoint.groupId ?? null;
367
+ itemAtPoint.element.classList.add('is-touch-drag-over');
368
+ } else {
369
+ dragOverIndex = -1;
370
+ dragOverGroup = null;
371
+ }
372
+ }
373
+
374
+ function handleTouchEnd() {
375
+ clearTimers();
376
+ isTouchPending = false;
377
+
378
+ if (!isTouchDragging || !draggedItem) {
379
+ isTouchDragging = false;
380
+ touchDraggedElement = null;
381
+ return;
382
+ }
383
+
384
+ containerEl
385
+ ?.querySelectorAll('.is-touch-drag-over')
386
+ .forEach((el) => el.classList.remove('is-touch-drag-over'));
387
+
388
+ const itemAtPoint = getItemAtPoint(touchCurrent.x, touchCurrent.y);
389
+ if (itemAtPoint) {
390
+ if (allowGroupTransfer || itemAtPoint.groupId === draggedFromGroup) {
391
+ performReorder(itemAtPoint.index, itemAtPoint.groupId);
392
+ }
393
+ }
394
+
395
+ touchDraggedElement?.classList.remove('is-dragging');
396
+ removeGhostElement();
397
+ touchDraggedElement = null;
398
+ resetDragState();
399
+ }
400
+
401
+ onMount(() => {
402
+ isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
403
+
404
+ const onGlobalTouchMove = (e: TouchEvent) => {
405
+ if (isTouchPending || isTouchDragging) {
406
+ handleTouchMove(e);
407
+ }
408
+ };
409
+
410
+ const onGlobalTouchEnd = () => {
411
+ if (isTouchPending || isTouchDragging) {
412
+ handleTouchEnd();
413
+ }
414
+ };
415
+
416
+ document.addEventListener('touchmove', onGlobalTouchMove, { passive: false });
417
+ document.addEventListener('touchend', onGlobalTouchEnd);
418
+ document.addEventListener('touchcancel', onGlobalTouchEnd);
419
+
420
+ return () => {
421
+ document.removeEventListener('touchmove', onGlobalTouchMove);
422
+ document.removeEventListener('touchend', onGlobalTouchEnd);
423
+ document.removeEventListener('touchcancel', onGlobalTouchEnd);
424
+ clearTimers();
425
+ };
426
+ });
427
+
428
+ function isItemDragging(item: DragDropItem) {
429
+ return draggedItem?.id === item.id;
430
+ }
431
+
432
+ function isItemDragOver(index: number, groupId?: string): boolean {
433
+ if (dragOverIndex !== index) return false;
434
+ if (dragOverGroup !== (groupId ?? null)) return false;
435
+ if (!draggedItem) return false;
436
+
437
+ const targetId = isSingleMode
438
+ ? items[index]?.id
439
+ : groups.find((g) => g.id === groupId)?.items[index]?.id;
440
+
441
+ return draggedItem.id !== targetId;
442
+ }
443
+ </script>
444
+
445
+ <div
446
+ bind:this={containerEl}
447
+ class={cn(
448
+ 'drag-drop',
449
+ colorClass[color],
450
+ variantClass[variant],
451
+ sizeClass[size],
452
+ direction === 'horizontal' ? 'is-horizontal' : 'is-vertical',
453
+ isDisabled && 'is-disabled',
454
+ isDragging && 'is-dragging',
455
+ isTouchDragging && 'is-touch-dragging',
456
+ className
457
+ )}
458
+ >
459
+ {#if isSingleMode}
460
+ <div class="drag-drop-list">
461
+ {#each items as item, index (item.id)}
462
+ <div
463
+ class={cn(
464
+ 'drag-drop-item',
465
+ isItemDragging(item) && 'is-dragging',
466
+ isItemDragOver(index) && 'is-drag-over',
467
+ itemClass
468
+ )}
469
+ draggable={!isDisabled && !isTouchDevice}
470
+ ondragstart={(e) => handleDragStart(e, item, index)}
471
+ ondragend={handleDragEnd}
472
+ ondragover={(e) => handleDragOver(e, index)}
473
+ ondragleave={handleDragLeave}
474
+ ondrop={(e) => handleDrop(e, index)}
475
+ ontouchstart={(e) => handleTouchStart(e, item, index)}
476
+ role="listitem"
477
+ >
478
+ {#if itemSnippet}
479
+ {@render itemSnippet(item, index)}
480
+ {:else}
481
+ <span class="drag-drop-item-content">{item.id}</span>
482
+ {/if}
483
+ <div class="drag-drop-handle">
484
+ <svg viewBox="0 0 24 24" fill="currentColor">
485
+ <circle cx="9" cy="6" r="1.5" />
486
+ <circle cx="15" cy="6" r="1.5" />
487
+ <circle cx="9" cy="12" r="1.5" />
488
+ <circle cx="15" cy="12" r="1.5" />
489
+ <circle cx="9" cy="18" r="1.5" />
490
+ <circle cx="15" cy="18" r="1.5" />
491
+ </svg>
492
+ </div>
493
+ </div>
494
+ {/each}
495
+
496
+ {#if items.length === 0}
497
+ <div
498
+ class="drag-drop-empty"
499
+ ondragover={(e) => e.preventDefault()}
500
+ ondrop={(e) => handleDropOnEmpty(e)}
501
+ role="listitem"
502
+ >
503
+ {#if emptySnippet}
504
+ {@render emptySnippet()}
505
+ {:else}
506
+ <span>{emptyText}</span>
507
+ {/if}
508
+ </div>
509
+ {/if}
510
+ </div>
511
+ {:else}
512
+ {#each groups as group, groupIndex (group.id)}
513
+ <div class={cn('drag-drop-group', groupClass)} data-group-id={group.id}>
514
+ {#if groupHeaderSnippet}
515
+ {@render groupHeaderSnippet(group, groupIndex)}
516
+ {:else if group.title}
517
+ <div class="drag-drop-group-header">
518
+ <span class="drag-drop-group-title">{group.title}</span>
519
+ <span class="drag-drop-group-count">{group.items.length}</span>
520
+ </div>
521
+ {/if}
522
+
523
+ <div class="drag-drop-list">
524
+ {#each group.items as item, index (item.id)}
525
+ <div
526
+ class={cn(
527
+ 'drag-drop-item',
528
+ isItemDragging(item) && 'is-dragging',
529
+ isItemDragOver(index, group.id) && 'is-drag-over',
530
+ itemClass
531
+ )}
532
+ draggable={!isDisabled && !isTouchDevice}
533
+ ondragstart={(e) => handleDragStart(e, item, index, group.id)}
534
+ ondragend={handleDragEnd}
535
+ ondragover={(e) => handleDragOver(e, index, group.id)}
536
+ ondragleave={handleDragLeave}
537
+ ondrop={(e) => handleDrop(e, index, group.id)}
538
+ ontouchstart={(e) => handleTouchStart(e, item, index, group.id)}
539
+ role="listitem"
540
+ >
541
+ {#if itemSnippet}
542
+ {@render itemSnippet(item, index)}
543
+ {:else}
544
+ <span class="drag-drop-item-content">{item.id}</span>
545
+ {/if}
546
+ <div class="drag-drop-handle">
547
+ <svg viewBox="0 0 24 24" fill="currentColor">
548
+ <circle cx="9" cy="6" r="1.5" />
549
+ <circle cx="15" cy="6" r="1.5" />
550
+ <circle cx="9" cy="12" r="1.5" />
551
+ <circle cx="15" cy="12" r="1.5" />
552
+ <circle cx="9" cy="18" r="1.5" />
553
+ <circle cx="15" cy="18" r="1.5" />
554
+ </svg>
555
+ </div>
556
+ </div>
557
+ {/each}
558
+
559
+ {#if group.items.length === 0}
560
+ <div
561
+ class={cn('drag-drop-empty', dragOverGroup === group.id && 'is-drag-over')}
562
+ ondragover={(e) => handleDragOver(e, 0, group.id)}
563
+ ondragleave={handleDragLeave}
564
+ ondrop={(e) => handleDropOnEmpty(e, group.id)}
565
+ role="listitem"
566
+ >
567
+ {#if emptySnippet}
568
+ {@render emptySnippet(group)}
569
+ {:else}
570
+ <span>{emptyText}</span>
571
+ {/if}
572
+ </div>
573
+ {/if}
574
+ </div>
575
+ </div>
576
+ {/each}
577
+ {/if}
578
+ </div>
@@ -0,0 +1,33 @@
1
+ export type DragDropItem = {
2
+ id: string;
3
+ [key: string]: unknown;
4
+ };
5
+ export type DragDropGroup = {
6
+ id: string;
7
+ title?: string;
8
+ items: DragDropItem[];
9
+ [key: string]: unknown;
10
+ };
11
+ import type { Snippet } from 'svelte';
12
+ type Props = {
13
+ groups?: DragDropGroup[];
14
+ items?: DragDropItem[];
15
+ class?: string;
16
+ groupClass?: string;
17
+ itemClass?: string;
18
+ color?: 'primary' | 'secondary' | 'muted' | 'success' | 'info' | 'danger' | 'warning';
19
+ variant?: 'soft' | 'solid' | 'outlined' | 'ghost';
20
+ size?: 'sm' | 'md' | 'lg';
21
+ direction?: 'horizontal' | 'vertical';
22
+ isDisabled?: boolean;
23
+ allowGroupTransfer?: boolean;
24
+ onReorder?: (items: DragDropItem[], fromGroup?: string, toGroup?: string) => void;
25
+ onGroupReorder?: (groups: DragDropGroup[]) => void;
26
+ itemSnippet?: Snippet<[DragDropItem, number]>;
27
+ groupHeaderSnippet?: Snippet<[DragDropGroup, number]>;
28
+ emptySnippet?: Snippet<[DragDropGroup?]>;
29
+ emptyText?: string;
30
+ };
31
+ declare const DragDrop: import("svelte").Component<Props, {}, "items" | "groups">;
32
+ type DragDrop = ReturnType<typeof DragDrop>;
33
+ export default DragDrop;
@@ -20,7 +20,17 @@
20
20
  class?: string;
21
21
  controlClass?: string;
22
22
  icon?: IconData;
23
- variant?: 'primary' | 'secondary' | 'muted' | 'outlined' | 'line';
23
+ color?:
24
+ | 'primary'
25
+ | 'secondary'
26
+ | 'muted'
27
+ | 'success'
28
+ | 'info'
29
+ | 'warning'
30
+ | 'danger'
31
+ | 'surface'
32
+ | 'background';
33
+ variant?: 'solid' | 'soft' | 'outlined' | 'ghost';
24
34
  size?: 'sm' | 'md' | 'lg';
25
35
  name?: string;
26
36
  label?: string;
@@ -29,7 +39,6 @@
29
39
  placeholder?: string;
30
40
  disabled?: boolean;
31
41
  onchange?: (files: FileWithUrl[]) => void;
32
- isSolid?: boolean;
33
42
  };
34
43
 
35
44
  let {
@@ -40,6 +49,7 @@
40
49
  class: className,
41
50
  controlClass,
42
51
  icon,
52
+ color = 'background',
43
53
  variant = 'outlined',
44
54
  size = 'md',
45
55
  name,
@@ -48,8 +58,7 @@
48
58
  errorText,
49
59
  placeholder = 'Drag & drop files here or click to select',
50
60
  disabled = false,
51
- onchange,
52
- isSolid
61
+ onchange
53
62
  }: Props = $props();
54
63
 
55
64
  const uid = $props.id();
@@ -57,12 +66,23 @@
57
66
  let isActive = $state(false);
58
67
  let isDragging = $state(false);
59
68
 
60
- const variantClasses = {
69
+ const colorClasses = {
61
70
  primary: 'is-primary',
62
71
  secondary: 'is-secondary',
63
72
  muted: 'is-muted',
73
+ success: 'is-success',
74
+ info: 'is-info',
75
+ danger: 'is-danger',
76
+ warning: 'is-warning',
77
+ surface: 'is-surface',
78
+ background: 'is-background'
79
+ };
80
+
81
+ const variantClasses = {
82
+ solid: 'is-solid',
83
+ soft: 'is-soft',
64
84
  outlined: 'is-outlined',
65
- line: 'is-line'
85
+ ghost: 'is-ghost'
66
86
  };
67
87
 
68
88
  const sizeClasses = {
@@ -118,9 +138,9 @@
118
138
  for={`${uid}-${name}`}
119
139
  class={cn(
120
140
  'dropzone-input',
141
+ colorClasses[color],
121
142
  variantClasses[variant],
122
143
  sizeClasses[size],
123
- isSolid && 'is-solid',
124
144
  (isActive || isDragging) && 'is-active',
125
145
  disabled && 'is-disabled',
126
146
  controlClass
@@ -179,7 +199,7 @@
179
199
  type="button"
180
200
  class="dropzone-file-remove"
181
201
  onclick={() => removeFile(i)}
182
- aria-label="Eliminar archivo"
202
+ aria-label="Remove file"
183
203
  >
184
204
  <Icon icon={Dismiss24RegularIcon} />
185
205
  </button>