@aspect-ops/exon-ui 0.0.2 → 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 (237) hide show
  1. package/README.md +793 -43
  2. package/dist/components/Accordion/Accordion.svelte +79 -0
  3. package/dist/components/Accordion/Accordion.svelte.d.ts +10 -0
  4. package/dist/components/Accordion/AccordionItem.svelte +198 -0
  5. package/dist/components/Accordion/AccordionItem.svelte.d.ts +10 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Accordion/index.js +2 -0
  8. package/dist/components/ActionSheet/ActionSheet.svelte +270 -0
  9. package/dist/components/ActionSheet/ActionSheet.svelte.d.ts +12 -0
  10. package/dist/components/ActionSheet/ActionSheetItem.svelte +151 -0
  11. package/dist/components/ActionSheet/ActionSheetItem.svelte.d.ts +10 -0
  12. package/dist/components/ActionSheet/index.d.ts +3 -0
  13. package/dist/components/ActionSheet/index.js +2 -0
  14. package/dist/components/Alert/Alert.svelte +165 -0
  15. package/dist/components/Alert/Alert.svelte.d.ts +11 -0
  16. package/dist/components/Alert/index.d.ts +1 -0
  17. package/dist/components/Alert/index.js +1 -0
  18. package/dist/components/AspectRatio/AspectRatio.svelte +42 -0
  19. package/dist/components/AspectRatio/AspectRatio.svelte.d.ts +9 -0
  20. package/dist/components/AspectRatio/index.d.ts +1 -0
  21. package/dist/components/AspectRatio/index.js +1 -0
  22. package/dist/components/Avatar/Avatar.svelte +147 -0
  23. package/dist/components/Avatar/Avatar.svelte.d.ts +12 -0
  24. package/dist/components/Avatar/AvatarGroup.svelte +153 -0
  25. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +12 -0
  26. package/dist/components/Avatar/index.d.ts +2 -0
  27. package/dist/components/Avatar/index.js +2 -0
  28. package/dist/components/BottomSheet/BottomSheet.svelte +230 -0
  29. package/dist/components/BottomSheet/BottomSheet.svelte.d.ts +7 -0
  30. package/dist/components/BottomSheet/BottomSheetBody.svelte +20 -0
  31. package/dist/components/BottomSheet/BottomSheetBody.svelte.d.ts +7 -0
  32. package/dist/components/BottomSheet/BottomSheetHeader.svelte +27 -0
  33. package/dist/components/BottomSheet/BottomSheetHeader.svelte.d.ts +7 -0
  34. package/dist/components/BottomSheet/index.d.ts +3 -0
  35. package/dist/components/BottomSheet/index.js +3 -0
  36. package/dist/components/Box/Box.svelte +41 -0
  37. package/dist/components/Box/Box.svelte.d.ts +7 -0
  38. package/dist/components/Box/index.d.ts +1 -0
  39. package/dist/components/Box/index.js +1 -0
  40. package/dist/components/Card/Card.svelte +95 -0
  41. package/dist/components/Card/Card.svelte.d.ts +10 -0
  42. package/dist/components/Card/CardBody.svelte +32 -0
  43. package/dist/components/Card/CardBody.svelte.d.ts +7 -0
  44. package/dist/components/Card/CardFooter.svelte +34 -0
  45. package/dist/components/Card/CardFooter.svelte.d.ts +7 -0
  46. package/dist/components/Card/CardHeader.svelte +67 -0
  47. package/dist/components/Card/CardHeader.svelte.d.ts +9 -0
  48. package/dist/components/Card/index.d.ts +4 -0
  49. package/dist/components/Card/index.js +4 -0
  50. package/dist/components/Carousel/Carousel.svelte +454 -0
  51. package/dist/components/Carousel/Carousel.svelte.d.ts +14 -0
  52. package/dist/components/Carousel/CarouselSlide.svelte +22 -0
  53. package/dist/components/Carousel/CarouselSlide.svelte.d.ts +7 -0
  54. package/dist/components/Carousel/index.d.ts +2 -0
  55. package/dist/components/Carousel/index.js +2 -0
  56. package/dist/components/Center/Center.svelte +28 -0
  57. package/dist/components/Center/Center.svelte.d.ts +8 -0
  58. package/dist/components/Center/index.d.ts +1 -0
  59. package/dist/components/Center/index.js +1 -0
  60. package/dist/components/Chip/Chip.svelte +461 -0
  61. package/dist/components/Chip/Chip.svelte.d.ts +17 -0
  62. package/dist/components/Chip/ChipGroup.svelte +76 -0
  63. package/dist/components/Chip/ChipGroup.svelte.d.ts +9 -0
  64. package/dist/components/Chip/index.d.ts +2 -0
  65. package/dist/components/Chip/index.js +2 -0
  66. package/dist/components/Container/Container.svelte +58 -0
  67. package/dist/components/Container/Container.svelte.d.ts +10 -0
  68. package/dist/components/Container/index.d.ts +1 -0
  69. package/dist/components/Container/index.js +1 -0
  70. package/dist/components/DatePicker/DatePicker.svelte +746 -0
  71. package/dist/components/DatePicker/DatePicker.svelte.d.ts +19 -0
  72. package/dist/components/DatePicker/index.d.ts +1 -0
  73. package/dist/components/DatePicker/index.js +1 -0
  74. package/dist/components/Divider/Divider.svelte +38 -0
  75. package/dist/components/Divider/Divider.svelte.d.ts +9 -0
  76. package/dist/components/Divider/index.d.ts +1 -0
  77. package/dist/components/Divider/index.js +1 -0
  78. package/dist/components/EmptyState/EmptyState.svelte +164 -0
  79. package/dist/components/EmptyState/EmptyState.svelte.d.ts +12 -0
  80. package/dist/components/EmptyState/index.d.ts +1 -0
  81. package/dist/components/EmptyState/index.js +1 -0
  82. package/dist/components/FAB/FAB.svelte +242 -0
  83. package/dist/components/FAB/FAB.svelte.d.ts +9 -0
  84. package/dist/components/FAB/FABGroup.svelte +449 -0
  85. package/dist/components/FAB/FABGroup.svelte.d.ts +9 -0
  86. package/dist/components/FAB/index.d.ts +3 -0
  87. package/dist/components/FAB/index.js +2 -0
  88. package/dist/components/FileUpload/FileUpload.svelte +484 -0
  89. package/dist/components/FileUpload/FileUpload.svelte.d.ts +16 -0
  90. package/dist/components/FileUpload/index.d.ts +1 -0
  91. package/dist/components/FileUpload/index.js +1 -0
  92. package/dist/components/Grid/Grid.svelte +136 -0
  93. package/dist/components/Grid/Grid.svelte.d.ts +12 -0
  94. package/dist/components/Grid/GridItem.svelte +21 -0
  95. package/dist/components/Grid/GridItem.svelte.d.ts +7 -0
  96. package/dist/components/Grid/index.d.ts +2 -0
  97. package/dist/components/Grid/index.js +2 -0
  98. package/dist/components/Image/Image.svelte +223 -0
  99. package/dist/components/Image/Image.svelte.d.ts +19 -0
  100. package/dist/components/Image/index.d.ts +1 -0
  101. package/dist/components/Image/index.js +1 -0
  102. package/dist/components/List/List.svelte +42 -0
  103. package/dist/components/List/List.svelte.d.ts +18 -0
  104. package/dist/components/List/ListItem.svelte +139 -0
  105. package/dist/components/List/ListItem.svelte.d.ts +36 -0
  106. package/dist/components/List/index.d.ts +2 -0
  107. package/dist/components/List/index.js +2 -0
  108. package/dist/components/Modal/Modal.svelte +204 -0
  109. package/dist/components/Modal/Modal.svelte.d.ts +7 -0
  110. package/dist/components/Modal/ModalBody.svelte +50 -0
  111. package/dist/components/Modal/ModalBody.svelte.d.ts +7 -0
  112. package/dist/components/Modal/ModalFooter.svelte +37 -0
  113. package/dist/components/Modal/ModalFooter.svelte.d.ts +7 -0
  114. package/dist/components/Modal/ModalHeader.svelte +73 -0
  115. package/dist/components/Modal/ModalHeader.svelte.d.ts +7 -0
  116. package/dist/components/Modal/index.d.ts +4 -0
  117. package/dist/components/Modal/index.js +4 -0
  118. package/dist/components/OTPInput/OTPInput.svelte +312 -0
  119. package/dist/components/OTPInput/OTPInput.svelte.d.ts +57 -0
  120. package/dist/components/OTPInput/index.d.ts +1 -0
  121. package/dist/components/OTPInput/index.js +1 -0
  122. package/dist/components/Popover/Popover.svelte +14 -0
  123. package/dist/components/Popover/Popover.svelte.d.ts +7 -0
  124. package/dist/components/Popover/PopoverContent.svelte +63 -0
  125. package/dist/components/Popover/PopoverContent.svelte.d.ts +7 -0
  126. package/dist/components/Popover/PopoverTrigger.svelte +14 -0
  127. package/dist/components/Popover/PopoverTrigger.svelte.d.ts +7 -0
  128. package/dist/components/Popover/index.d.ts +3 -0
  129. package/dist/components/Popover/index.js +3 -0
  130. package/dist/components/Progress/ProgressBar.svelte +86 -0
  131. package/dist/components/Progress/ProgressBar.svelte.d.ts +11 -0
  132. package/dist/components/Progress/ProgressCircle.svelte +134 -0
  133. package/dist/components/Progress/ProgressCircle.svelte.d.ts +12 -0
  134. package/dist/components/Progress/Spinner.svelte +68 -0
  135. package/dist/components/Progress/Spinner.svelte.d.ts +8 -0
  136. package/dist/components/Progress/index.d.ts +3 -0
  137. package/dist/components/Progress/index.js +3 -0
  138. package/dist/components/PullToRefresh/PullToRefresh.svelte +304 -0
  139. package/dist/components/PullToRefresh/PullToRefresh.svelte.d.ts +20 -0
  140. package/dist/components/PullToRefresh/index.d.ts +1 -0
  141. package/dist/components/PullToRefresh/index.js +1 -0
  142. package/dist/components/Rating/Rating.svelte +316 -0
  143. package/dist/components/Rating/Rating.svelte.d.ts +16 -0
  144. package/dist/components/Rating/index.d.ts +1 -0
  145. package/dist/components/Rating/index.js +1 -0
  146. package/dist/components/SafeArea/SafeArea.svelte +33 -0
  147. package/dist/components/SafeArea/SafeArea.svelte.d.ts +7 -0
  148. package/dist/components/SearchInput/SearchInput.svelte +480 -0
  149. package/dist/components/SearchInput/SearchInput.svelte.d.ts +22 -0
  150. package/dist/components/SearchInput/index.d.ts +1 -0
  151. package/dist/components/SearchInput/index.js +1 -0
  152. package/dist/components/Select/Select.svelte +55 -12
  153. package/dist/components/Skeleton/Skeleton.svelte +59 -0
  154. package/dist/components/Skeleton/Skeleton.svelte.d.ts +10 -0
  155. package/dist/components/Skeleton/index.d.ts +1 -0
  156. package/dist/components/Skeleton/index.js +1 -0
  157. package/dist/components/Slider/Slider.svelte +324 -0
  158. package/dist/components/Slider/Slider.svelte.d.ts +14 -0
  159. package/dist/components/Slider/index.d.ts +1 -0
  160. package/dist/components/Slider/index.js +1 -0
  161. package/dist/components/Spacer/Spacer.svelte +56 -0
  162. package/dist/components/Spacer/Spacer.svelte.d.ts +6 -0
  163. package/dist/components/Spacer/index.d.ts +1 -0
  164. package/dist/components/Spacer/index.js +1 -0
  165. package/dist/components/Stack/Stack.svelte +117 -0
  166. package/dist/components/Stack/Stack.svelte.d.ts +13 -0
  167. package/dist/components/Stack/index.d.ts +1 -0
  168. package/dist/components/Stack/index.js +1 -0
  169. package/dist/components/Stepper/Stepper.svelte +100 -0
  170. package/dist/components/Stepper/Stepper.svelte.d.ts +11 -0
  171. package/dist/components/Stepper/StepperStep.svelte +391 -0
  172. package/dist/components/Stepper/StepperStep.svelte.d.ts +13 -0
  173. package/dist/components/Stepper/index.d.ts +2 -0
  174. package/dist/components/Stepper/index.js +2 -0
  175. package/dist/components/SwipeActions/SwipeAction.svelte +43 -0
  176. package/dist/components/SwipeActions/SwipeAction.svelte.d.ts +8 -0
  177. package/dist/components/SwipeActions/SwipeActions.svelte +193 -0
  178. package/dist/components/SwipeActions/SwipeActions.svelte.d.ts +9 -0
  179. package/dist/components/SwipeActions/index.d.ts +2 -0
  180. package/dist/components/SwipeActions/index.js +2 -0
  181. package/dist/components/Switch/Switch.svelte +29 -9
  182. package/dist/components/Table/Table.svelte +175 -0
  183. package/dist/components/Table/Table.svelte.d.ts +38 -0
  184. package/dist/components/Table/TableBody.svelte +26 -0
  185. package/dist/components/Table/TableBody.svelte.d.ts +13 -0
  186. package/dist/components/Table/TableCell.svelte +85 -0
  187. package/dist/components/Table/TableCell.svelte.d.ts +28 -0
  188. package/dist/components/Table/TableHead.svelte +36 -0
  189. package/dist/components/Table/TableHead.svelte.d.ts +13 -0
  190. package/dist/components/Table/TableHeader.svelte +217 -0
  191. package/dist/components/Table/TableHeader.svelte.d.ts +32 -0
  192. package/dist/components/Table/TableRow.svelte +92 -0
  193. package/dist/components/Table/TableRow.svelte.d.ts +28 -0
  194. package/dist/components/Table/index.d.ts +6 -0
  195. package/dist/components/Table/index.js +6 -0
  196. package/dist/components/Tag/Tag.svelte +189 -0
  197. package/dist/components/Tag/Tag.svelte.d.ts +13 -0
  198. package/dist/components/Tag/index.d.ts +1 -0
  199. package/dist/components/Tag/index.js +1 -0
  200. package/dist/components/TimePicker/TimePicker.svelte +803 -0
  201. package/dist/components/TimePicker/TimePicker.svelte.d.ts +17 -0
  202. package/dist/components/TimePicker/index.d.ts +1 -0
  203. package/dist/components/TimePicker/index.js +1 -0
  204. package/dist/components/Toast/Toast.svelte +241 -0
  205. package/dist/components/Toast/Toast.svelte.d.ts +18 -0
  206. package/dist/components/Toast/ToastContainer.svelte +110 -0
  207. package/dist/components/Toast/ToastContainer.svelte.d.ts +8 -0
  208. package/dist/components/Toast/index.d.ts +3 -0
  209. package/dist/components/Toast/index.js +3 -0
  210. package/dist/components/Toast/toast.d.ts +13 -0
  211. package/dist/components/Toast/toast.js +55 -0
  212. package/dist/components/Tooltip/Tooltip.svelte +71 -0
  213. package/dist/components/Tooltip/Tooltip.svelte.d.ts +7 -0
  214. package/dist/components/Tooltip/index.d.ts +2 -0
  215. package/dist/components/Tooltip/index.js +1 -0
  216. package/dist/index.d.ts +38 -1
  217. package/dist/index.js +41 -0
  218. package/dist/styles/tokens.css +5 -0
  219. package/dist/types/data-display.d.ts +161 -0
  220. package/dist/types/data-display.js +1 -0
  221. package/dist/types/feedback.d.ts +92 -0
  222. package/dist/types/feedback.js +1 -0
  223. package/dist/types/index.d.ts +6 -1
  224. package/dist/types/input.d.ts +67 -0
  225. package/dist/types/input.js +2 -0
  226. package/dist/types/layout.d.ts +57 -0
  227. package/dist/types/layout.js +1 -0
  228. package/dist/types/mobile.d.ts +91 -0
  229. package/dist/types/mobile.js +1 -0
  230. package/dist/types/navigation.d.ts +15 -0
  231. package/dist/utils/gestures.d.ts +219 -0
  232. package/dist/utils/gestures.js +492 -0
  233. package/dist/utils/haptics.d.ts +89 -0
  234. package/dist/utils/haptics.js +198 -0
  235. package/dist/utils/platform.d.ts +47 -0
  236. package/dist/utils/platform.js +156 -0
  237. package/package.json +1 -1
@@ -0,0 +1,746 @@
1
+ <script lang="ts">
2
+ import type { InputSize } from '../../types/index.js';
3
+ import { onMount } from 'svelte';
4
+
5
+ interface Props {
6
+ value?: Date | null;
7
+ placeholder?: string;
8
+ min?: Date | null;
9
+ max?: Date | null;
10
+ disabled?: boolean;
11
+ required?: boolean;
12
+ error?: boolean;
13
+ size?: InputSize;
14
+ format?: string;
15
+ locale?: string;
16
+ class?: string;
17
+ onchange?: (date: Date | null) => void;
18
+ onclear?: () => void;
19
+ }
20
+
21
+ let {
22
+ value = $bindable(null),
23
+ placeholder = 'Select date',
24
+ min = null,
25
+ max = null,
26
+ disabled = false,
27
+ required = false,
28
+ error = false,
29
+ size = 'md',
30
+ format = 'yyyy-MM-dd',
31
+ locale = 'en-US',
32
+ class: className = '',
33
+ onchange,
34
+ onclear
35
+ }: Props = $props();
36
+
37
+ let isOpen = $state(false);
38
+ let inputElement: HTMLInputElement | undefined;
39
+ let calendarElement = $state<HTMLDivElement | undefined>(undefined);
40
+ let currentMonth = $state(
41
+ value ? new Date(value.getFullYear(), value.getMonth(), 1) : new Date()
42
+ );
43
+ let focusedDate = $state<Date | null>(null);
44
+
45
+ const formattedValue = $derived(value ? formatDate(value, format, locale) : '');
46
+
47
+ function formatDate(date: Date, fmt: string, loc: string): string {
48
+ const year = date.getFullYear();
49
+ const month = String(date.getMonth() + 1).padStart(2, '0');
50
+ const day = String(date.getDate()).padStart(2, '0');
51
+
52
+ // Simple format replacements
53
+ return fmt
54
+ .replace('yyyy', String(year))
55
+ .replace('yy', String(year).slice(-2))
56
+ .replace('MM', month)
57
+ .replace('M', String(date.getMonth() + 1))
58
+ .replace('dd', day)
59
+ .replace('d', String(date.getDate()));
60
+ }
61
+
62
+ function getDaysInMonth(date: Date): Date[] {
63
+ const year = date.getFullYear();
64
+ const month = date.getMonth();
65
+ const firstDay = new Date(year, month, 1);
66
+ const lastDay = new Date(year, month + 1, 0);
67
+
68
+ const days: Date[] = [];
69
+
70
+ // Add padding days from previous month
71
+ const firstDayOfWeek = firstDay.getDay();
72
+ for (let i = firstDayOfWeek - 1; i >= 0; i--) {
73
+ const prevDate = new Date(year, month, -i);
74
+ days.push(prevDate);
75
+ }
76
+
77
+ // Add current month days
78
+ for (let day = 1; day <= lastDay.getDate(); day++) {
79
+ days.push(new Date(year, month, day));
80
+ }
81
+
82
+ // Add padding days from next month
83
+ const remainingDays = 42 - days.length; // 6 weeks * 7 days
84
+ for (let i = 1; i <= remainingDays; i++) {
85
+ days.push(new Date(year, month + 1, i));
86
+ }
87
+
88
+ return days;
89
+ }
90
+
91
+ const calendarDays = $derived(getDaysInMonth(currentMonth));
92
+
93
+ function isDateDisabled(date: Date): boolean {
94
+ if (min && date < min) return true;
95
+ if (max && date > max) return true;
96
+ return false;
97
+ }
98
+
99
+ function isToday(date: Date): boolean {
100
+ const today = new Date();
101
+ return (
102
+ date.getDate() === today.getDate() &&
103
+ date.getMonth() === today.getMonth() &&
104
+ date.getFullYear() === today.getFullYear()
105
+ );
106
+ }
107
+
108
+ function isSelected(date: Date): boolean {
109
+ if (!value) return false;
110
+ return (
111
+ date.getDate() === value.getDate() &&
112
+ date.getMonth() === value.getMonth() &&
113
+ date.getFullYear() === value.getFullYear()
114
+ );
115
+ }
116
+
117
+ function isCurrentMonth(date: Date): boolean {
118
+ return date.getMonth() === currentMonth.getMonth();
119
+ }
120
+
121
+ function selectDate(date: Date) {
122
+ if (isDateDisabled(date) || disabled) return;
123
+ value = date;
124
+ onchange?.(date);
125
+ isOpen = false;
126
+ inputElement?.focus();
127
+ }
128
+
129
+ function clearDate() {
130
+ value = null;
131
+ onclear?.();
132
+ onchange?.(null);
133
+ inputElement?.focus();
134
+ }
135
+
136
+ function openCalendar() {
137
+ if (disabled) return;
138
+ isOpen = true;
139
+ // Set current month to selected date or today
140
+ currentMonth = value ? new Date(value.getFullYear(), value.getMonth(), 1) : new Date();
141
+ focusedDate = value || new Date();
142
+ }
143
+
144
+ function closeCalendar() {
145
+ isOpen = false;
146
+ focusedDate = null;
147
+ }
148
+
149
+ function previousMonth() {
150
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1);
151
+ focusedDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
152
+ }
153
+
154
+ function nextMonth() {
155
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
156
+ focusedDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
157
+ }
158
+
159
+ function handleKeyDown(e: KeyboardEvent) {
160
+ if (!isOpen || !focusedDate) return;
161
+
162
+ const currentIndex = calendarDays.findIndex(
163
+ (d) =>
164
+ d.getDate() === focusedDate!.getDate() &&
165
+ d.getMonth() === focusedDate!.getMonth() &&
166
+ d.getFullYear() === focusedDate!.getFullYear()
167
+ );
168
+
169
+ if (currentIndex === -1) return;
170
+
171
+ let newIndex = currentIndex;
172
+
173
+ switch (e.key) {
174
+ case 'ArrowLeft':
175
+ e.preventDefault();
176
+ newIndex = Math.max(0, currentIndex - 1);
177
+ break;
178
+ case 'ArrowRight':
179
+ e.preventDefault();
180
+ newIndex = Math.min(calendarDays.length - 1, currentIndex + 1);
181
+ break;
182
+ case 'ArrowUp':
183
+ e.preventDefault();
184
+ newIndex = Math.max(0, currentIndex - 7);
185
+ break;
186
+ case 'ArrowDown':
187
+ e.preventDefault();
188
+ newIndex = Math.min(calendarDays.length - 1, currentIndex + 7);
189
+ break;
190
+ case 'Enter':
191
+ e.preventDefault();
192
+ selectDate(focusedDate);
193
+ return;
194
+ case 'Escape':
195
+ e.preventDefault();
196
+ closeCalendar();
197
+ return;
198
+ case 'Tab':
199
+ // Allow normal tab navigation
200
+ return;
201
+ }
202
+
203
+ if (newIndex !== currentIndex) {
204
+ focusedDate = calendarDays[newIndex];
205
+ // Change month if necessary
206
+ if (focusedDate.getMonth() !== currentMonth.getMonth()) {
207
+ currentMonth = new Date(focusedDate.getFullYear(), focusedDate.getMonth(), 1);
208
+ }
209
+ }
210
+ }
211
+
212
+ function handleClickOutside(e: MouseEvent) {
213
+ if (!isOpen) return;
214
+ const target = e.target as Node;
215
+ if (
216
+ calendarElement &&
217
+ !calendarElement.contains(target) &&
218
+ inputElement &&
219
+ !inputElement.contains(target)
220
+ ) {
221
+ closeCalendar();
222
+ }
223
+ }
224
+
225
+ onMount(() => {
226
+ document.addEventListener('click', handleClickOutside);
227
+ return () => {
228
+ document.removeEventListener('click', handleClickOutside);
229
+ };
230
+ });
231
+
232
+ const monthYearLabel = $derived(
233
+ new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(currentMonth)
234
+ );
235
+ </script>
236
+
237
+ <div class="datepicker datepicker--{size} {className}">
238
+ <div class="datepicker__input-wrapper">
239
+ <input
240
+ bind:this={inputElement}
241
+ type="text"
242
+ role="combobox"
243
+ class="datepicker__input"
244
+ class:datepicker__input--error={error}
245
+ class:datepicker__input--disabled={disabled}
246
+ value={formattedValue}
247
+ {placeholder}
248
+ {disabled}
249
+ {required}
250
+ readonly
251
+ aria-invalid={error}
252
+ aria-required={required}
253
+ aria-haspopup="dialog"
254
+ aria-expanded={isOpen}
255
+ aria-controls="datepicker-calendar"
256
+ onclick={openCalendar}
257
+ onkeydown={(e) => {
258
+ if (e.key === 'Enter' || e.key === ' ') {
259
+ e.preventDefault();
260
+ openCalendar();
261
+ }
262
+ }}
263
+ />
264
+
265
+ <div class="datepicker__icons">
266
+ {#if value && !disabled}
267
+ <button
268
+ type="button"
269
+ class="datepicker__clear"
270
+ aria-label="Clear date"
271
+ onclick={(e) => {
272
+ e.stopPropagation();
273
+ clearDate();
274
+ }}
275
+ >
276
+ <svg
277
+ width="16"
278
+ height="16"
279
+ viewBox="0 0 16 16"
280
+ fill="none"
281
+ xmlns="http://www.w3.org/2000/svg"
282
+ >
283
+ <path
284
+ d="M12 4L4 12M4 4L12 12"
285
+ stroke="currentColor"
286
+ stroke-width="1.5"
287
+ stroke-linecap="round"
288
+ stroke-linejoin="round"
289
+ />
290
+ </svg>
291
+ </button>
292
+ {/if}
293
+
294
+ <button
295
+ type="button"
296
+ class="datepicker__calendar-icon"
297
+ aria-label="Open calendar"
298
+ {disabled}
299
+ onclick={openCalendar}
300
+ >
301
+ <svg
302
+ width="16"
303
+ height="16"
304
+ viewBox="0 0 16 16"
305
+ fill="none"
306
+ xmlns="http://www.w3.org/2000/svg"
307
+ >
308
+ <path
309
+ d="M12.6667 2.66667H3.33333C2.59695 2.66667 2 3.26362 2 4V13.3333C2 14.0697 2.59695 14.6667 3.33333 14.6667H12.6667C13.403 14.6667 14 14.0697 14 13.3333V4C14 3.26362 13.403 2.66667 12.6667 2.66667Z"
310
+ stroke="currentColor"
311
+ stroke-width="1.5"
312
+ stroke-linecap="round"
313
+ stroke-linejoin="round"
314
+ />
315
+ <path
316
+ d="M10.6667 1.33333V4"
317
+ stroke="currentColor"
318
+ stroke-width="1.5"
319
+ stroke-linecap="round"
320
+ stroke-linejoin="round"
321
+ />
322
+ <path
323
+ d="M5.33333 1.33333V4"
324
+ stroke="currentColor"
325
+ stroke-width="1.5"
326
+ stroke-linecap="round"
327
+ stroke-linejoin="round"
328
+ />
329
+ <path
330
+ d="M2 6.66667H14"
331
+ stroke="currentColor"
332
+ stroke-width="1.5"
333
+ stroke-linecap="round"
334
+ stroke-linejoin="round"
335
+ />
336
+ </svg>
337
+ </button>
338
+ </div>
339
+ </div>
340
+
341
+ {#if isOpen}
342
+ <div
343
+ bind:this={calendarElement}
344
+ id="datepicker-calendar"
345
+ class="datepicker__calendar"
346
+ role="dialog"
347
+ aria-label="Choose a date"
348
+ aria-modal="false"
349
+ tabindex="-1"
350
+ onkeydown={handleKeyDown}
351
+ >
352
+ <div class="datepicker__header">
353
+ <button
354
+ type="button"
355
+ class="datepicker__nav-button"
356
+ aria-label="Previous month"
357
+ onclick={previousMonth}
358
+ tabindex={0}
359
+ >
360
+ <svg
361
+ width="16"
362
+ height="16"
363
+ viewBox="0 0 16 16"
364
+ fill="none"
365
+ xmlns="http://www.w3.org/2000/svg"
366
+ >
367
+ <path
368
+ d="M10 12L6 8L10 4"
369
+ stroke="currentColor"
370
+ stroke-width="1.5"
371
+ stroke-linecap="round"
372
+ stroke-linejoin="round"
373
+ />
374
+ </svg>
375
+ </button>
376
+
377
+ <span class="datepicker__month-year" aria-live="polite">
378
+ {monthYearLabel}
379
+ </span>
380
+
381
+ <button
382
+ type="button"
383
+ class="datepicker__nav-button"
384
+ aria-label="Next month"
385
+ onclick={nextMonth}
386
+ tabindex={0}
387
+ >
388
+ <svg
389
+ width="16"
390
+ height="16"
391
+ viewBox="0 0 16 16"
392
+ fill="none"
393
+ xmlns="http://www.w3.org/2000/svg"
394
+ >
395
+ <path
396
+ d="M6 4L10 8L6 12"
397
+ stroke="currentColor"
398
+ stroke-width="1.5"
399
+ stroke-linecap="round"
400
+ stroke-linejoin="round"
401
+ />
402
+ </svg>
403
+ </button>
404
+ </div>
405
+
406
+ <div class="datepicker__weekdays">
407
+ {#each ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as day}
408
+ <span class="datepicker__weekday" aria-label={day}>{day}</span>
409
+ {/each}
410
+ </div>
411
+
412
+ <div class="datepicker__days">
413
+ {#each calendarDays as day}
414
+ <button
415
+ type="button"
416
+ class="datepicker__day"
417
+ class:datepicker__day--today={isToday(day)}
418
+ class:datepicker__day--selected={isSelected(day)}
419
+ class:datepicker__day--disabled={isDateDisabled(day)}
420
+ class:datepicker__day--outside-month={!isCurrentMonth(day)}
421
+ class:datepicker__day--focused={focusedDate &&
422
+ day.getDate() === focusedDate.getDate() &&
423
+ day.getMonth() === focusedDate.getMonth() &&
424
+ day.getFullYear() === focusedDate.getFullYear()}
425
+ disabled={isDateDisabled(day)}
426
+ aria-label={new Intl.DateTimeFormat(locale, {
427
+ weekday: 'long',
428
+ year: 'numeric',
429
+ month: 'long',
430
+ day: 'numeric'
431
+ }).format(day)}
432
+ aria-current={isSelected(day) ? 'date' : undefined}
433
+ aria-disabled={isDateDisabled(day)}
434
+ tabindex={focusedDate &&
435
+ day.getDate() === focusedDate.getDate() &&
436
+ day.getMonth() === focusedDate.getMonth() &&
437
+ day.getFullYear() === focusedDate.getFullYear()
438
+ ? 0
439
+ : -1}
440
+ onclick={() => selectDate(day)}
441
+ onfocus={() => {
442
+ focusedDate = day;
443
+ }}
444
+ >
445
+ {day.getDate()}
446
+ </button>
447
+ {/each}
448
+ </div>
449
+ </div>
450
+ {/if}
451
+ </div>
452
+
453
+ <style>
454
+ .datepicker {
455
+ position: relative;
456
+ width: 100%;
457
+ }
458
+
459
+ .datepicker__input-wrapper {
460
+ position: relative;
461
+ display: flex;
462
+ align-items: center;
463
+ width: 100%;
464
+ }
465
+
466
+ .datepicker__input {
467
+ width: 100%;
468
+ min-height: var(--touch-target-min, 44px);
469
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
470
+ padding-right: 4rem;
471
+ border: 1px solid var(--color-border, #e5e7eb);
472
+ border-radius: var(--radius-md, 0.375rem);
473
+ background: var(--color-bg, #ffffff);
474
+ color: var(--color-text, #1f2937);
475
+ font-family: inherit;
476
+ font-size: var(--text-sm, 0.875rem);
477
+ line-height: 1.5;
478
+ cursor: pointer;
479
+ transition:
480
+ border-color var(--transition-fast, 150ms ease),
481
+ box-shadow var(--transition-fast, 150ms ease);
482
+ -webkit-tap-highlight-color: transparent;
483
+ }
484
+
485
+ .datepicker__input::placeholder {
486
+ color: var(--color-text-muted, #9ca3af);
487
+ }
488
+
489
+ .datepicker__input:focus {
490
+ outline: none;
491
+ border-color: var(--color-primary, #3b82f6);
492
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
493
+ }
494
+
495
+ .datepicker__input--disabled {
496
+ background: var(--color-bg-muted, #f3f4f6);
497
+ cursor: not-allowed;
498
+ opacity: 0.5;
499
+ }
500
+
501
+ .datepicker__input--error {
502
+ border-color: var(--color-error, #ef4444);
503
+ }
504
+
505
+ .datepicker__input--error:focus {
506
+ border-color: var(--color-error, #ef4444);
507
+ box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
508
+ }
509
+
510
+ /* Sizes */
511
+ .datepicker--sm .datepicker__input {
512
+ min-height: var(--touch-target-min, 44px);
513
+ padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
514
+ padding-right: 3.5rem;
515
+ font-size: var(--text-xs, 0.75rem);
516
+ }
517
+
518
+ .datepicker--md .datepicker__input {
519
+ min-height: var(--touch-target-min, 44px);
520
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
521
+ padding-right: 4rem;
522
+ font-size: var(--text-sm, 0.875rem);
523
+ }
524
+
525
+ .datepicker--lg .datepicker__input {
526
+ min-height: 3.25rem;
527
+ padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
528
+ padding-right: 4.5rem;
529
+ font-size: var(--text-base, 1rem);
530
+ }
531
+
532
+ /* Icons */
533
+ .datepicker__icons {
534
+ position: absolute;
535
+ right: var(--space-md, 1rem);
536
+ display: flex;
537
+ align-items: center;
538
+ gap: var(--space-xs, 0.25rem);
539
+ }
540
+
541
+ .datepicker__clear,
542
+ .datepicker__calendar-icon {
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ min-width: var(--touch-target-min, 44px);
547
+ min-height: var(--touch-target-min, 44px);
548
+ padding: var(--space-xs, 0.25rem);
549
+ border: none;
550
+ background: transparent;
551
+ color: var(--color-text-muted, #6b7280);
552
+ cursor: pointer;
553
+ border-radius: var(--radius-sm, 0.25rem);
554
+ transition: background var(--transition-fast, 150ms ease);
555
+ -webkit-tap-highlight-color: transparent;
556
+ }
557
+
558
+ .datepicker__clear:hover,
559
+ .datepicker__calendar-icon:hover {
560
+ background: var(--color-bg-muted, #f3f4f6);
561
+ color: var(--color-text, #1f2937);
562
+ }
563
+
564
+ .datepicker__calendar-icon:disabled {
565
+ cursor: not-allowed;
566
+ opacity: 0.5;
567
+ }
568
+
569
+ /* Calendar */
570
+ .datepicker__calendar {
571
+ position: absolute;
572
+ top: calc(100% + 0.5rem);
573
+ left: 0;
574
+ z-index: 50;
575
+ min-width: 18rem;
576
+ padding: var(--space-md, 1rem);
577
+ border: 1px solid var(--color-border, #e5e7eb);
578
+ border-radius: var(--radius-md, 0.375rem);
579
+ background: var(--color-bg, #ffffff);
580
+ box-shadow: var(--shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));
581
+ animation: calendar-in 150ms ease-out;
582
+ font-family: var(--font-family, system-ui, -apple-system, sans-serif);
583
+ }
584
+
585
+ @keyframes calendar-in {
586
+ from {
587
+ opacity: 0;
588
+ transform: translateY(-4px);
589
+ }
590
+ to {
591
+ opacity: 1;
592
+ transform: translateY(0);
593
+ }
594
+ }
595
+
596
+ .datepicker__header {
597
+ display: flex;
598
+ align-items: center;
599
+ justify-content: space-between;
600
+ margin-bottom: var(--space-md, 1rem);
601
+ }
602
+
603
+ .datepicker__nav-button {
604
+ display: flex;
605
+ align-items: center;
606
+ justify-content: center;
607
+ min-width: var(--touch-target-min, 44px);
608
+ min-height: var(--touch-target-min, 44px);
609
+ padding: var(--space-xs, 0.25rem);
610
+ border: none;
611
+ background: transparent;
612
+ color: var(--color-text, #1f2937);
613
+ cursor: pointer;
614
+ border-radius: var(--radius-sm, 0.25rem);
615
+ transition: background var(--transition-fast, 150ms ease);
616
+ -webkit-tap-highlight-color: transparent;
617
+ }
618
+
619
+ .datepicker__nav-button:hover {
620
+ background: var(--color-bg-muted, #f3f4f6);
621
+ }
622
+
623
+ .datepicker__nav-button:focus {
624
+ outline: none;
625
+ box-shadow: 0 0 0 2px var(--color-primary-alpha, rgba(59, 130, 246, 0.2));
626
+ }
627
+
628
+ .datepicker__month-year {
629
+ font-size: var(--text-sm, 0.875rem);
630
+ font-weight: 600;
631
+ color: var(--color-text, #1f2937);
632
+ }
633
+
634
+ .datepicker__weekdays {
635
+ display: grid;
636
+ grid-template-columns: repeat(7, 1fr);
637
+ gap: var(--space-xs, 0.25rem);
638
+ margin-bottom: var(--space-sm, 0.5rem);
639
+ }
640
+
641
+ .datepicker__weekday {
642
+ display: flex;
643
+ align-items: center;
644
+ justify-content: center;
645
+ padding: var(--space-xs, 0.25rem);
646
+ font-size: var(--text-xs, 0.75rem);
647
+ font-weight: 600;
648
+ color: var(--color-text-muted, #6b7280);
649
+ text-align: center;
650
+ }
651
+
652
+ .datepicker__days {
653
+ display: grid;
654
+ grid-template-columns: repeat(7, 1fr);
655
+ gap: var(--space-xs, 0.25rem);
656
+ }
657
+
658
+ .datepicker__day {
659
+ display: flex;
660
+ align-items: center;
661
+ justify-content: center;
662
+ min-width: var(--touch-target-min, 44px);
663
+ min-height: var(--touch-target-min, 44px);
664
+ padding: var(--space-xs, 0.25rem);
665
+ border: none;
666
+ background: transparent;
667
+ color: var(--color-text, #1f2937);
668
+ font-size: var(--text-sm, 0.875rem);
669
+ cursor: pointer;
670
+ border-radius: var(--radius-sm, 0.25rem);
671
+ transition:
672
+ background var(--transition-fast, 150ms ease),
673
+ color var(--transition-fast, 150ms ease);
674
+ -webkit-tap-highlight-color: transparent;
675
+ }
676
+
677
+ .datepicker__day:hover:not(:disabled) {
678
+ background: var(--color-bg-muted, #f3f4f6);
679
+ }
680
+
681
+ .datepicker__day:focus {
682
+ outline: none;
683
+ }
684
+
685
+ .datepicker__day--focused {
686
+ box-shadow: 0 0 0 2px var(--color-primary-alpha, rgba(59, 130, 246, 0.2));
687
+ }
688
+
689
+ .datepicker__day--today {
690
+ font-weight: 600;
691
+ color: var(--color-primary, #3b82f6);
692
+ }
693
+
694
+ .datepicker__day--selected {
695
+ background: var(--color-primary, #3b82f6);
696
+ color: var(--color-text-inverse, #ffffff);
697
+ font-weight: 600;
698
+ }
699
+
700
+ .datepicker__day--selected:hover {
701
+ background: var(--color-primary, #3b82f6);
702
+ color: var(--color-text-inverse, #ffffff);
703
+ }
704
+
705
+ .datepicker__day--disabled {
706
+ color: var(--color-text-muted, #9ca3af);
707
+ cursor: not-allowed;
708
+ opacity: 0.5;
709
+ }
710
+
711
+ .datepicker__day--disabled:hover {
712
+ background: transparent;
713
+ }
714
+
715
+ .datepicker__day--outside-month {
716
+ color: var(--color-text-muted, #9ca3af);
717
+ opacity: 0.5;
718
+ }
719
+
720
+ /* Mobile responsive */
721
+ @media (max-width: 640px) {
722
+ .datepicker__calendar {
723
+ position: fixed;
724
+ top: auto;
725
+ bottom: 0;
726
+ left: 0;
727
+ right: 0;
728
+ min-width: 100%;
729
+ max-width: 100%;
730
+ border-radius: 1rem 1rem 0 0;
731
+ animation: calendar-slide-up 250ms ease-out;
732
+ padding-bottom: calc(var(--space-md, 1rem) + env(safe-area-inset-bottom, 0));
733
+ }
734
+
735
+ @keyframes calendar-slide-up {
736
+ from {
737
+ opacity: 0;
738
+ transform: translateY(100%);
739
+ }
740
+ to {
741
+ opacity: 1;
742
+ transform: translateY(0);
743
+ }
744
+ }
745
+ }
746
+ </style>