@rkosafo/cai.components 0.0.1 → 0.0.3

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 (255) hide show
  1. package/dist/baseEditor/index.svelte +32 -0
  2. package/dist/baseEditor/index.svelte.d.ts +18 -0
  3. package/dist/builders/filters/FilterBuilder.svelte +638 -0
  4. package/dist/builders/filters/FilterBuilder.svelte.d.ts +4 -0
  5. package/dist/builders/filters/index.d.ts +1 -0
  6. package/dist/builders/filters/index.js +1 -0
  7. package/dist/forms/FormCheckbox/FormCheckbox.svelte +53 -0
  8. package/dist/forms/FormCheckbox/FormCheckbox.svelte.d.ts +4 -0
  9. package/dist/forms/FormCheckbox/index.d.ts +1 -0
  10. package/dist/forms/FormCheckbox/index.js +1 -0
  11. package/dist/forms/FormDatepicker/FormDatepicker.svelte +159 -0
  12. package/dist/forms/FormDatepicker/FormDatepicker.svelte.d.ts +13 -0
  13. package/dist/forms/FormDatepicker/index.d.ts +1 -0
  14. package/dist/forms/FormDatepicker/index.js +1 -0
  15. package/dist/forms/FormInput/FormInput.svelte +87 -0
  16. package/dist/forms/FormInput/FormInput.svelte.d.ts +4 -0
  17. package/dist/forms/FormInput/index.d.ts +1 -0
  18. package/dist/forms/FormInput/index.js +1 -0
  19. package/dist/forms/FormRadio/FormRadio.svelte +53 -0
  20. package/dist/forms/FormRadio/FormRadio.svelte.d.ts +4 -0
  21. package/dist/forms/FormRadio/index.d.ts +1 -0
  22. package/dist/forms/FormRadio/index.js +1 -0
  23. package/dist/forms/FormSelect/FormSelect.svelte +86 -0
  24. package/dist/forms/FormSelect/FormSelect.svelte.d.ts +4 -0
  25. package/dist/forms/FormSelect/index.d.ts +1 -0
  26. package/dist/forms/FormSelect/index.js +1 -0
  27. package/dist/forms/FormTextarea/FormTextarea.svelte +77 -0
  28. package/dist/forms/FormTextarea/FormTextarea.svelte.d.ts +4 -0
  29. package/dist/forms/FormTextarea/index.d.ts +1 -0
  30. package/dist/forms/FormTextarea/index.js +1 -0
  31. package/dist/forms/checkbox/Checkbox.svelte +82 -0
  32. package/dist/forms/checkbox/Checkbox.svelte.d.ts +4 -0
  33. package/dist/forms/checkbox/CheckboxButton.svelte +92 -0
  34. package/dist/forms/checkbox/CheckboxButton.svelte.d.ts +18 -0
  35. package/dist/forms/checkbox/index.d.ts +3 -0
  36. package/dist/forms/checkbox/index.js +3 -0
  37. package/dist/forms/checkbox/theme.d.ts +317 -0
  38. package/dist/forms/checkbox/theme.js +113 -0
  39. package/dist/forms/datepicker/Datepicker.svelte +706 -0
  40. package/dist/forms/datepicker/Datepicker.svelte.d.ts +41 -0
  41. package/dist/forms/datepicker/index.d.ts +2 -0
  42. package/dist/forms/datepicker/index.js +2 -0
  43. package/dist/forms/datepicker/theme.d.ts +385 -0
  44. package/dist/forms/datepicker/theme.js +56 -0
  45. package/dist/forms/form/Form.svelte +69 -0
  46. package/dist/forms/form/Form.svelte.d.ts +6 -0
  47. package/dist/forms/form/index.d.ts +2 -0
  48. package/dist/forms/form/index.js +2 -0
  49. package/dist/forms/input/Input.svelte +363 -0
  50. package/dist/forms/input/Input.svelte.d.ts +4 -0
  51. package/dist/forms/input/index.d.ts +4 -0
  52. package/dist/forms/input/index.js +5 -0
  53. package/dist/forms/input/theme.d.ts +301 -0
  54. package/dist/forms/input/theme.js +100 -0
  55. package/dist/forms/label/Label.svelte +38 -0
  56. package/dist/forms/label/Label.svelte.d.ts +15 -0
  57. package/dist/forms/label/index.d.ts +2 -0
  58. package/dist/forms/label/index.js +2 -0
  59. package/dist/forms/label/theme.d.ts +75 -0
  60. package/dist/forms/label/theme.js +29 -0
  61. package/dist/forms/radio/Radio.svelte +48 -0
  62. package/dist/forms/radio/Radio.svelte.d.ts +25 -0
  63. package/dist/forms/radio/RadioButton.svelte +22 -0
  64. package/dist/forms/radio/RadioButton.svelte.d.ts +25 -0
  65. package/dist/forms/radio/index.d.ts +3 -0
  66. package/dist/forms/radio/index.js +3 -0
  67. package/dist/forms/radio/theme.d.ts +290 -0
  68. package/dist/forms/radio/theme.js +95 -0
  69. package/dist/forms/select/Select.svelte +50 -0
  70. package/dist/forms/select/Select.svelte.d.ts +4 -0
  71. package/dist/forms/select/index.d.ts +1 -0
  72. package/dist/forms/select/index.js +1 -0
  73. package/dist/forms/textarea/Textarea.svelte +165 -0
  74. package/dist/forms/textarea/Textarea.svelte.d.ts +4 -0
  75. package/dist/forms/textarea/index.d.ts +2 -0
  76. package/dist/forms/textarea/index.js +2 -0
  77. package/dist/forms/textarea/theme.d.ts +100 -0
  78. package/dist/forms/textarea/theme.js +35 -0
  79. package/dist/index.d.ts +42 -2
  80. package/dist/index.js +42 -2
  81. package/dist/layout/TF/Content/Content.svelte +28 -0
  82. package/dist/layout/TF/Content/Content.svelte.d.ts +8 -0
  83. package/dist/layout/TF/Content/index.d.ts +1 -0
  84. package/dist/layout/TF/Content/index.js +1 -0
  85. package/dist/layout/TF/Header/Header.svelte +159 -0
  86. package/dist/layout/TF/Header/Header.svelte.d.ts +21 -0
  87. package/dist/layout/TF/Header/index.d.ts +1 -0
  88. package/dist/layout/TF/Header/index.js +1 -0
  89. package/dist/layout/TF/Sidebar/Sidebar.svelte +74 -0
  90. package/dist/layout/TF/Sidebar/Sidebar.svelte.d.ts +23 -0
  91. package/dist/layout/TF/Sidebar/index.d.ts +1 -0
  92. package/dist/layout/TF/Sidebar/index.js +1 -0
  93. package/dist/layout/TF/Wrapper/Wrapper.svelte +17 -0
  94. package/dist/layout/TF/Wrapper/Wrapper.svelte.d.ts +8 -0
  95. package/dist/layout/TF/Wrapper/index.d.ts +1 -0
  96. package/dist/layout/TF/Wrapper/index.js +1 -0
  97. package/dist/layout/TF/index.d.ts +5 -0
  98. package/dist/layout/TF/index.js +5 -0
  99. package/dist/themes/ThemeProvider.svelte +20 -0
  100. package/dist/themes/ThemeProvider.svelte.d.ts +9 -0
  101. package/dist/themes/index.d.ts +7 -0
  102. package/dist/themes/index.js +1 -0
  103. package/dist/themes/themeUtils.d.ts +24 -0
  104. package/dist/themes/themeUtils.js +74 -0
  105. package/dist/themes/themes.d.ts +18 -0
  106. package/dist/themes/themes.js +18 -0
  107. package/dist/types/index.d.ts +755 -0
  108. package/dist/types/index.js +1 -0
  109. package/dist/typography/heading/Heading.svelte +35 -0
  110. package/dist/typography/heading/Heading.svelte.d.ts +10 -0
  111. package/dist/typography/heading/index.d.ts +2 -0
  112. package/dist/typography/heading/index.js +2 -0
  113. package/dist/typography/heading/theme.d.ts +30 -0
  114. package/dist/typography/heading/theme.js +17 -0
  115. package/dist/ui/accordion/Accordion.svelte +49 -0
  116. package/dist/ui/accordion/Accordion.svelte.d.ts +4 -0
  117. package/dist/ui/accordion/AccordionItem.svelte +173 -0
  118. package/dist/ui/accordion/AccordionItem.svelte.d.ts +4 -0
  119. package/dist/ui/accordion/index.d.ts +3 -0
  120. package/dist/ui/accordion/index.js +3 -0
  121. package/dist/ui/accordion/theme.d.ts +96 -0
  122. package/dist/ui/accordion/theme.js +59 -0
  123. package/dist/ui/alert/Alert.svelte +83 -0
  124. package/dist/ui/alert/Alert.svelte.d.ts +5 -0
  125. package/dist/ui/alert/index.d.ts +2 -0
  126. package/dist/ui/alert/index.js +2 -0
  127. package/dist/ui/alert/theme.d.ts +108 -0
  128. package/dist/ui/alert/theme.js +149 -0
  129. package/dist/ui/alertDialog/AlertDialog.svelte +40 -0
  130. package/dist/ui/alertDialog/AlertDialog.svelte.d.ts +4 -0
  131. package/dist/ui/alertDialog/index.d.ts +1 -0
  132. package/dist/ui/alertDialog/index.js +1 -0
  133. package/dist/ui/avatar/Avatar.svelte +77 -0
  134. package/dist/ui/avatar/Avatar.svelte.d.ts +4 -0
  135. package/dist/ui/avatar/index.d.ts +2 -0
  136. package/dist/ui/avatar/index.js +2 -0
  137. package/dist/ui/avatar/theme.d.ts +63 -0
  138. package/dist/ui/avatar/theme.js +31 -0
  139. package/dist/ui/buttons/Button.svelte +102 -0
  140. package/dist/ui/buttons/Button.svelte.d.ts +4 -0
  141. package/dist/ui/buttons/GradientButton.svelte +59 -0
  142. package/dist/ui/buttons/GradientButton.svelte.d.ts +4 -0
  143. package/dist/ui/buttons/index.d.ts +3 -0
  144. package/dist/ui/buttons/index.js +3 -0
  145. package/dist/ui/buttons/theme.d.ts +704 -0
  146. package/dist/ui/buttons/theme.js +332 -0
  147. package/dist/ui/datatable/Datatable.svelte +516 -0
  148. package/dist/ui/datatable/Datatable.svelte.d.ts +5 -0
  149. package/dist/ui/datatable/index.d.ts +2 -0
  150. package/dist/ui/datatable/index.js +2 -0
  151. package/dist/ui/drawer/Drawer.svelte +280 -0
  152. package/dist/ui/drawer/Drawer.svelte.d.ts +37 -0
  153. package/dist/ui/drawer/index.d.ts +2 -0
  154. package/dist/ui/drawer/index.js +2 -0
  155. package/dist/ui/drawer/theme.d.ts +211 -0
  156. package/dist/ui/drawer/theme.js +46 -0
  157. package/dist/ui/dropdown/Dropdown.svelte +36 -0
  158. package/dist/ui/dropdown/Dropdown.svelte.d.ts +4 -0
  159. package/dist/ui/dropdown/DropdownDivider.svelte +11 -0
  160. package/dist/ui/dropdown/DropdownDivider.svelte.d.ts +4 -0
  161. package/dist/ui/dropdown/DropdownGroup.svelte +14 -0
  162. package/dist/ui/dropdown/DropdownGroup.svelte.d.ts +4 -0
  163. package/dist/ui/dropdown/DropdownHeader.svelte +14 -0
  164. package/dist/ui/dropdown/DropdownHeader.svelte.d.ts +4 -0
  165. package/dist/ui/dropdown/DropdownItem.svelte +52 -0
  166. package/dist/ui/dropdown/DropdownItem.svelte.d.ts +4 -0
  167. package/dist/ui/dropdown/index.d.ts +6 -0
  168. package/dist/ui/dropdown/index.js +6 -0
  169. package/dist/ui/dropdown/theme.d.ts +55 -0
  170. package/dist/ui/dropdown/theme.js +20 -0
  171. package/dist/ui/footer/Footer.svelte +15 -0
  172. package/dist/ui/footer/Footer.svelte.d.ts +4 -0
  173. package/dist/ui/footer/FooterBrand.svelte +37 -0
  174. package/dist/ui/footer/FooterBrand.svelte.d.ts +4 -0
  175. package/dist/ui/footer/FooterCopyright.svelte +45 -0
  176. package/dist/ui/footer/FooterCopyright.svelte.d.ts +4 -0
  177. package/dist/ui/footer/FooterIcon.svelte +22 -0
  178. package/dist/ui/footer/FooterIcon.svelte.d.ts +4 -0
  179. package/dist/ui/footer/FooterLink.svelte +33 -0
  180. package/dist/ui/footer/FooterLink.svelte.d.ts +4 -0
  181. package/dist/ui/footer/FooterLinkGroup.svelte +13 -0
  182. package/dist/ui/footer/FooterLinkGroup.svelte.d.ts +4 -0
  183. package/dist/ui/footer/index.d.ts +7 -0
  184. package/dist/ui/footer/index.js +7 -0
  185. package/dist/ui/footer/theme.d.ts +137 -0
  186. package/dist/ui/footer/theme.js +39 -0
  187. package/dist/ui/indicator/Indicator.svelte +42 -0
  188. package/dist/ui/indicator/Indicator.svelte.d.ts +4 -0
  189. package/dist/ui/indicator/index.d.ts +2 -0
  190. package/dist/ui/indicator/index.js +2 -0
  191. package/dist/ui/indicator/theme.d.ts +177 -0
  192. package/dist/ui/indicator/theme.js +114 -0
  193. package/dist/ui/modal/Modal.svelte +265 -0
  194. package/dist/ui/modal/Modal.svelte.d.ts +38 -0
  195. package/dist/ui/modal/index.d.ts +2 -0
  196. package/dist/ui/modal/index.js +2 -0
  197. package/dist/ui/modal/theme.d.ts +190 -0
  198. package/dist/ui/modal/theme.js +41 -0
  199. package/dist/ui/notificationList/NotificationList.svelte +123 -0
  200. package/dist/ui/notificationList/NotificationList.svelte.d.ts +25 -0
  201. package/dist/ui/notificationList/index.d.ts +1 -0
  202. package/dist/ui/notificationList/index.js +1 -0
  203. package/dist/ui/pageLoader/PageLoader.svelte +10 -0
  204. package/dist/ui/pageLoader/PageLoader.svelte.d.ts +4 -0
  205. package/dist/ui/pageLoader/index.d.ts +1 -0
  206. package/dist/ui/pageLoader/index.js +1 -0
  207. package/dist/ui/paginate/Paginate.svelte +96 -0
  208. package/dist/ui/paginate/Paginate.svelte.d.ts +4 -0
  209. package/dist/ui/paginate/index.d.ts +1 -0
  210. package/dist/ui/paginate/index.js +1 -0
  211. package/dist/ui/tab/Tab.svelte +65 -0
  212. package/dist/ui/tab/Tab.svelte.d.ts +4 -0
  213. package/dist/ui/tab/index.d.ts +2 -0
  214. package/dist/ui/tab/index.js +2 -0
  215. package/dist/ui/tab/theme.d.ts +135 -0
  216. package/dist/ui/tab/theme.js +83 -0
  217. package/dist/ui/table/Table.svelte +385 -0
  218. package/dist/ui/table/Table.svelte.d.ts +4 -0
  219. package/dist/ui/table/index.d.ts +1 -0
  220. package/dist/ui/table/index.js +1 -0
  221. package/dist/ui/tableLoader/TableLoader.svelte +24 -0
  222. package/dist/ui/tableLoader/TableLoader.svelte.d.ts +13 -0
  223. package/dist/ui/tableLoader/index.d.ts +1 -0
  224. package/dist/ui/tableLoader/index.js +1 -0
  225. package/dist/ui/toolbar/Toolbar.svelte +59 -0
  226. package/dist/ui/toolbar/Toolbar.svelte.d.ts +17 -0
  227. package/dist/ui/toolbar/ToolbarButton.svelte +56 -0
  228. package/dist/ui/toolbar/ToolbarButton.svelte.d.ts +17 -0
  229. package/dist/ui/toolbar/ToolbarGroup.svelte +43 -0
  230. package/dist/ui/toolbar/ToolbarGroup.svelte.d.ts +16 -0
  231. package/dist/ui/toolbar/index.d.ts +4 -0
  232. package/dist/ui/toolbar/index.js +4 -0
  233. package/dist/ui/toolbar/theme.d.ts +320 -0
  234. package/dist/ui/toolbar/theme.js +155 -0
  235. package/dist/utils/Popper.svelte +257 -0
  236. package/dist/utils/Popper.svelte.d.ts +4 -0
  237. package/dist/utils/action.d.ts +16 -0
  238. package/dist/utils/action.js +107 -0
  239. package/dist/utils/closeButton/CloseButton.svelte +88 -0
  240. package/dist/utils/closeButton/CloseButton.svelte.d.ts +12 -0
  241. package/dist/utils/closeButton/index.d.ts +2 -0
  242. package/dist/utils/closeButton/index.js +2 -0
  243. package/dist/utils/closeButton/theme.d.ts +100 -0
  244. package/dist/utils/closeButton/theme.js +69 -0
  245. package/dist/utils/dismissable.d.ts +9 -0
  246. package/dist/utils/dismissable.js +16 -0
  247. package/dist/utils/index.d.ts +8 -0
  248. package/dist/utils/index.js +35 -0
  249. package/dist/utils/paginate.svelte.d.ts +22 -0
  250. package/dist/utils/paginate.svelte.js +167 -0
  251. package/dist/utils/singleSelection.svelte.d.ts +15 -0
  252. package/dist/utils/singleSelection.svelte.js +49 -0
  253. package/dist/utils/svelte-legos.d.ts +7 -0
  254. package/dist/utils/svelte-legos.js +14 -0
  255. package/package.json +24 -2
@@ -0,0 +1,706 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { fade } from 'svelte/transition';
4
+ import clsx from 'clsx';
5
+ // import { Button, ToolbarButton, type DatepickerProps } from '../..';
6
+ import { datepicker } from './index.js';
7
+ import {
8
+ parse,
9
+ isValid,
10
+ addDays,
11
+ startOfMonth,
12
+ endOfMonth,
13
+ startOfWeek,
14
+ endOfWeek,
15
+ eachDayOfInterval,
16
+ isSameDay,
17
+ isWithinInterval
18
+ } from 'date-fns';
19
+ import { Button, getTheme, ToolbarButton, type DatepickerProps } from '../../index.js';
20
+
21
+ let {
22
+ value = $bindable(),
23
+ defaultDate = null,
24
+ range = false,
25
+ rangeFrom = $bindable(),
26
+ rangeTo = $bindable(),
27
+ availableFrom = null,
28
+ availableTo = null,
29
+ locale = 'default',
30
+ translationLocale = locale,
31
+ firstDayOfWeek = 0,
32
+ dateFormat,
33
+ placeholder = 'Select date',
34
+ disabled = false,
35
+ required = false,
36
+ inputClass = '',
37
+ color = 'primary',
38
+ inline = false,
39
+ autohide = true,
40
+ showActionButtons = false,
41
+ title = '',
42
+ onselect,
43
+ onclear,
44
+ onapply,
45
+ btnClass,
46
+ inputmode = 'none',
47
+ classes,
48
+ monthColor = 'alternative',
49
+ monthBtnSelected = 'bg-primary-500 text-white',
50
+ monthBtn = 'text-gray-700 dark:text-gray-300',
51
+ class: className,
52
+ elementRef = $bindable(),
53
+ name
54
+ }: DatepickerProps = $props();
55
+
56
+ const theme = getTheme('datepicker');
57
+
58
+ // If translationLocale is not explicitly provided, it will default to the value of locale. This ensures reactivity as both are directly exposed as props.
59
+ translationLocale = translationLocale ?? locale;
60
+
61
+ let isOpen: boolean = $state(inline);
62
+ let showMonthSelector: boolean = $state(false);
63
+ let inputElement: HTMLInputElement | null = $state(null);
64
+
65
+ $effect(() => {
66
+ if (inputElement) {
67
+ elementRef = inputElement;
68
+ }
69
+ });
70
+ let datepickerContainerElement: HTMLDivElement;
71
+ let currentMonth: Date = $state(value || defaultDate || new Date());
72
+ let focusedDate: Date | null = null;
73
+ let calendarRef: HTMLDivElement | null = $state(null);
74
+
75
+ let daysInMonth = $derived(getDaysInMonth(currentMonth));
76
+
77
+ onMount(() => {
78
+ if (!inline) {
79
+ datepickerContainerElement?.ownerDocument.addEventListener('click', handleClickOutside);
80
+ return () => {
81
+ datepickerContainerElement?.ownerDocument.removeEventListener('click', handleClickOutside);
82
+ };
83
+ }
84
+ });
85
+
86
+ function getDaysInMonth(date: Date): Date[] {
87
+ const monthStart = startOfMonth(date);
88
+ const monthEnd = endOfMonth(date);
89
+ const calendarStart = startOfWeek(monthStart, { weekStartsOn: firstDayOfWeek });
90
+ const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: firstDayOfWeek });
91
+
92
+ return eachDayOfInterval({ start: calendarStart, end: calendarEnd });
93
+ }
94
+
95
+ const getWeekdayNames = (): string[] => {
96
+ const referenceDate = new Date(1970, 0, 4 + firstDayOfWeek);
97
+ return Array.from({ length: 7 }, (_, i) =>
98
+ addDays(referenceDate, i).toLocaleDateString(translationLocale, { weekday: 'short' })
99
+ );
100
+ };
101
+
102
+ let weekdays = $derived(getWeekdayNames());
103
+
104
+ const getMonthNames = (): string[] => {
105
+ return Array.from({ length: 12 }, (_, i) =>
106
+ new Date(2000, i, 1).toLocaleDateString(translationLocale, { month: 'short' })
107
+ );
108
+ };
109
+ let monthNames = $derived(getMonthNames());
110
+
111
+ const addDay = (date: Date, increment: number): Date => addDays(date, increment);
112
+
113
+ function changeMonth(increment: number) {
114
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + increment, 1);
115
+ }
116
+
117
+ function changeYear(increment: number) {
118
+ currentMonth = new Date(currentMonth.getFullYear() + increment, currentMonth.getMonth(), 1);
119
+ }
120
+
121
+ function selectMonth(monthIndex: number, event: MouseEvent) {
122
+ event.stopPropagation();
123
+ currentMonth = new Date(currentMonth.getFullYear(), monthIndex, 1);
124
+ showMonthSelector = false;
125
+ }
126
+
127
+ function toggleMonthSelector(event: MouseEvent) {
128
+ event.stopPropagation();
129
+ showMonthSelector = !showMonthSelector;
130
+ }
131
+
132
+ function isDateAvailable(date: Date): boolean {
133
+ const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
134
+
135
+ if (availableFrom) {
136
+ const fromDate = new Date(
137
+ availableFrom.getFullYear(),
138
+ availableFrom.getMonth(),
139
+ availableFrom.getDate()
140
+ );
141
+ if (dateOnly < fromDate) return false;
142
+ }
143
+
144
+ if (availableTo) {
145
+ const toDate = new Date(
146
+ availableTo.getFullYear(),
147
+ availableTo.getMonth(),
148
+ availableTo.getDate()
149
+ );
150
+ if (dateOnly > toDate) return false;
151
+ }
152
+
153
+ return true;
154
+ }
155
+
156
+ function handleDaySelect(day: Date) {
157
+ if (!isDateAvailable(day)) return;
158
+
159
+ if (range) {
160
+ if (!rangeFrom || (rangeFrom && rangeTo)) {
161
+ rangeFrom = day;
162
+ rangeTo = undefined;
163
+ } else if (day < rangeFrom) {
164
+ rangeFrom = day;
165
+ rangeTo = rangeFrom;
166
+ } else {
167
+ rangeTo = day;
168
+ }
169
+ onselect?.({ from: rangeFrom, to: rangeTo });
170
+ } else {
171
+ value = day;
172
+ onselect?.(value);
173
+ if (autohide && !inline) isOpen = false;
174
+ }
175
+ }
176
+
177
+ function handleInputChangeWithDateFns() {
178
+ const inputValue = inputElement?.value?.trim();
179
+ if (!inputValue) {
180
+ rangeFrom = undefined;
181
+ rangeTo = undefined;
182
+ inputElement?.setCustomValidity('');
183
+ return;
184
+ }
185
+
186
+ inputElement?.setCustomValidity('');
187
+
188
+ if (range) {
189
+ const parts = inputValue.split(' - ');
190
+ if (parts.length === 2) {
191
+ const parsedFrom = tryParseDate(parts[0]);
192
+ const parsedTo = tryParseDate(parts[1]);
193
+
194
+ if (
195
+ parsedFrom &&
196
+ isValid(parsedFrom) &&
197
+ isDateAvailable(parsedFrom) &&
198
+ parsedTo &&
199
+ isValid(parsedTo) &&
200
+ isDateAvailable(parsedTo)
201
+ ) {
202
+ [rangeFrom, rangeTo] =
203
+ parsedFrom > parsedTo ? [parsedTo, parsedFrom] : [parsedFrom, parsedTo];
204
+ onselect?.({ from: rangeFrom, to: rangeTo });
205
+ return;
206
+ } else {
207
+ inputElement?.setCustomValidity(
208
+ `Please enter date range in format: ${getDateFormatPattern()} - ${getDateFormatPattern()}`
209
+ );
210
+ return;
211
+ }
212
+ }
213
+ }
214
+
215
+ const parsedDate = tryParseDate(inputValue);
216
+
217
+ if (!parsedDate || !isValid(parsedDate)) {
218
+ const formatPattern = getDateFormatPattern();
219
+ inputElement?.setCustomValidity(`Please enter date in format: ${formatPattern}`);
220
+ return;
221
+ }
222
+
223
+ if (!isDateAvailable(parsedDate)) {
224
+ inputElement?.setCustomValidity('Selected date is not available');
225
+ return;
226
+ }
227
+
228
+ handleDaySelect(parsedDate);
229
+ }
230
+
231
+ function tryParseDate(inputValue: string): Date | null {
232
+ const formatPattern = getDateFormatPattern();
233
+ try {
234
+ const parsedDate = parse(inputValue, formatPattern, new Date());
235
+ if (isValid(parsedDate)) {
236
+ return parsedDate;
237
+ }
238
+ } catch (error) {
239
+ // Continue to next strategy
240
+ }
241
+
242
+ const commonFormats = [
243
+ 'd.M.yyyy', // German: 17.7.2025
244
+ 'dd.MM.yyyy', // German: 17.07.2025
245
+ 'M/d/yyyy', // US: 7/17/2025
246
+ 'MM/dd/yyyy', // US: 07/17/2025
247
+ 'd/M/yyyy', // UK: 17/7/2025
248
+ 'dd/MM/yyyy', // UK: 17/07/2025
249
+ 'yyyy-MM-dd', // ISO: 2025-07-17
250
+ 'yyyy-M-d', // ISO: 2025-7-17
251
+ 'M-d-yyyy', // US with dashes: 7-17-2025
252
+ 'd-M-yyyy' // EU with dashes: 17-7-2025
253
+ ];
254
+
255
+ for (const format of commonFormats) {
256
+ try {
257
+ const parsedDate = parse(inputValue, format, new Date());
258
+ if (isValid(parsedDate)) {
259
+ return parsedDate;
260
+ }
261
+ } catch (error) {
262
+ // Continue to next format
263
+ }
264
+ }
265
+
266
+ try {
267
+ const nativeDate = new Date(inputValue);
268
+ if (isValid(nativeDate) && !isNaN(nativeDate.getTime())) {
269
+ return nativeDate;
270
+ }
271
+ } catch (error) {
272
+ // Continue
273
+ }
274
+
275
+ return null;
276
+ }
277
+
278
+ function getDateFormatPattern(): string {
279
+ const actualLocale = locale === 'default' ? navigator.language : locale;
280
+ const testDate = new Date(2025, 0, 15); // January 15, 2025
281
+ const formatted = testDate.toLocaleDateString(
282
+ actualLocale,
283
+ dateFormat || { year: 'numeric', month: 'numeric', day: 'numeric' }
284
+ );
285
+
286
+ if (formatted.includes('.')) {
287
+ // German/European format with dots
288
+ if (formatted.startsWith('15.')) {
289
+ return 'd.M.yyyy';
290
+ } else if (formatted.startsWith('01.')) {
291
+ return 'M.d.yyyy';
292
+ }
293
+ return 'd.M.yyyy'; // Default to day first
294
+ } else if (formatted.includes('/')) {
295
+ // US/UK format with slashes
296
+ if (formatted.startsWith('1/')) {
297
+ return 'M/d/yyyy'; // US format
298
+ } else if (formatted.startsWith('15/')) {
299
+ return 'd/M/yyyy'; // UK format
300
+ }
301
+
302
+ const testDate2 = new Date(2025, 11, 3); // December 3, 2025
303
+ const formatted2 = testDate2.toLocaleDateString(
304
+ actualLocale,
305
+ dateFormat || { year: 'numeric', month: 'numeric', day: 'numeric' }
306
+ );
307
+ if (formatted2.startsWith('3/') || formatted2.startsWith('03/')) {
308
+ return 'd/M/yyyy';
309
+ } else {
310
+ return 'M/d/yyyy';
311
+ }
312
+ } else if (formatted.includes('-')) {
313
+ // ISO or other dash format
314
+ if (formatted.startsWith('2025-')) {
315
+ return 'yyyy-M-d';
316
+ } else if (formatted.startsWith('1-')) {
317
+ return 'M-d-yyyy';
318
+ } else {
319
+ return 'd-M-yyyy';
320
+ }
321
+ }
322
+
323
+ // Default fallback - try to detect based on locale
324
+ if (actualLocale.startsWith('en-US')) {
325
+ return 'M/d/yyyy';
326
+ } else if (
327
+ actualLocale.startsWith('de') ||
328
+ actualLocale.startsWith('at') ||
329
+ actualLocale.startsWith('ch')
330
+ ) {
331
+ return 'd.M.yyyy';
332
+ } else if (actualLocale.startsWith('en-GB') || actualLocale.startsWith('en-AU')) {
333
+ return 'd/M/yyyy';
334
+ }
335
+
336
+ return 'M/d/yyyy';
337
+ }
338
+
339
+ function handleClickOutside(event: MouseEvent) {
340
+ if (
341
+ isOpen &&
342
+ datepickerContainerElement &&
343
+ !datepickerContainerElement.contains(event.target as Node)
344
+ ) {
345
+ isOpen = false;
346
+ showMonthSelector = false;
347
+ }
348
+ }
349
+
350
+ // Use locale for formatting (not translationLocale)
351
+ const formatDate = (date?: Date): string => date?.toLocaleDateString(locale, dateFormat) ?? '';
352
+ const isSameDate = (date1?: Date, date2?: Date): boolean =>
353
+ date1 && date2 ? isSameDay(date1, date2) : false;
354
+ const isToday = (day: Date): boolean => isSameDate(day, new Date());
355
+ const isInRange = (day: Date): boolean =>
356
+ !!(range && rangeFrom && rangeTo && isWithinInterval(day, { start: rangeFrom, end: rangeTo }));
357
+
358
+ let isSelected = $derived((day: Date): boolean =>
359
+ range ? isSameDate(day, rangeFrom) || isSameDate(day, rangeTo) : isSameDate(day, value)
360
+ );
361
+
362
+ function handleCalendarKeydown(event: KeyboardEvent) {
363
+ if (!isOpen) return;
364
+
365
+ if (!focusedDate) {
366
+ focusedDate = value || new Date();
367
+ }
368
+
369
+ switch (event.key) {
370
+ case 'ArrowLeft':
371
+ focusedDate = addDay(focusedDate, -1);
372
+ break;
373
+ case 'ArrowRight':
374
+ focusedDate = addDay(focusedDate, 1);
375
+ break;
376
+ case 'ArrowUp':
377
+ focusedDate = addDay(focusedDate, -7);
378
+ break;
379
+ case 'ArrowDown':
380
+ focusedDate = addDay(focusedDate, 7);
381
+ break;
382
+ case 'Enter':
383
+ if (range) {
384
+ if (rangeFrom && rangeTo) {
385
+ if (autohide && !inline) isOpen = false;
386
+ } else {
387
+ handleDaySelect(focusedDate);
388
+ }
389
+ } else {
390
+ handleDaySelect(focusedDate);
391
+ if (autohide && !inline) isOpen = false;
392
+ }
393
+ break;
394
+ case 'Escape':
395
+ isOpen = false;
396
+ showMonthSelector = false;
397
+ inputElement?.focus();
398
+ break;
399
+ default:
400
+ return;
401
+ }
402
+
403
+ event.preventDefault();
404
+ if (focusedDate.getMonth() !== currentMonth.getMonth()) {
405
+ currentMonth = new Date(focusedDate.getFullYear(), focusedDate.getMonth(), 1);
406
+ }
407
+
408
+ // Use translationLocale for aria-label
409
+ setTimeout(() => {
410
+ const focusedButton = calendarRef?.querySelector(
411
+ `button[aria-label="${focusedDate!.toLocaleDateString(translationLocale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}"]`
412
+ ) as HTMLButtonElement | null;
413
+ focusedButton?.focus();
414
+ }, 0);
415
+ }
416
+
417
+ function handleInputKeydown(event: KeyboardEvent) {
418
+ if (event.key === 'Enter') {
419
+ event.preventDefault();
420
+ handleInputChangeWithDateFns();
421
+ if (autohide && !inline) {
422
+ isOpen = false;
423
+ }
424
+ } else if (event.key === ' ') {
425
+ event.preventDefault();
426
+ isOpen = !isOpen;
427
+ }
428
+ }
429
+
430
+ function handleClear() {
431
+ value = rangeFrom = rangeTo = undefined;
432
+ onclear?.();
433
+ }
434
+
435
+ function handleApply() {
436
+ const result = range ? { from: rangeFrom, to: rangeTo } : value;
437
+ if (result) onapply?.(result);
438
+ if (!inline) isOpen = false;
439
+ }
440
+
441
+ let {
442
+ base,
443
+ input,
444
+ button,
445
+ titleVariant,
446
+ actionButtons,
447
+ columnHeader,
448
+ polite,
449
+ grid,
450
+ nav,
451
+ dayButton,
452
+ monthButton
453
+ } = datepicker();
454
+ </script>
455
+
456
+ {#snippet navButton(forward: boolean)}
457
+ <ToolbarButton
458
+ color="dark"
459
+ onclick={() => changeMonth(forward ? 1 : -1)}
460
+ size="lg"
461
+ aria-label={forward ? 'Next month' : 'Previous month'}
462
+ >
463
+ <svg
464
+ class="h-3 w-3 rtl:rotate-180"
465
+ aria-hidden="true"
466
+ xmlns="http://www.w3.org/2000/svg"
467
+ fill="none"
468
+ viewBox="0 0 14 10"
469
+ >
470
+ <path
471
+ stroke="currentColor"
472
+ stroke-linecap="round"
473
+ stroke-linejoin="round"
474
+ stroke-width="2"
475
+ d={forward ? 'M1 5h12m0 0L9 1m4 4L9 9' : 'M13 5H1m0 0 4 4M1 5l4-4'}
476
+ ></path>
477
+ </svg>
478
+ </ToolbarButton>
479
+ {/snippet}
480
+
481
+ {#snippet yearNavButton(forward: boolean)}
482
+ <ToolbarButton
483
+ color="dark"
484
+ onclick={() => changeYear(forward ? 1 : -1)}
485
+ size="lg"
486
+ aria-label={forward ? 'Next year' : 'Previous year'}
487
+ >
488
+ <svg
489
+ class="h-3 w-3 rtl:rotate-180"
490
+ aria-hidden="true"
491
+ xmlns="http://www.w3.org/2000/svg"
492
+ fill="none"
493
+ viewBox="0 0 14 10"
494
+ >
495
+ <path
496
+ stroke="currentColor"
497
+ stroke-linecap="round"
498
+ stroke-linejoin="round"
499
+ stroke-width="2"
500
+ d={forward ? 'M1 5h12m0 0L9 1m4 4L9 9' : 'M13 5H1m0 0 4 4M1 5l4-4'}
501
+ ></path>
502
+ </svg>
503
+ </ToolbarButton>
504
+ {/snippet}
505
+
506
+ <div bind:this={datepickerContainerElement} class={['relative', inline && 'inline-block']}>
507
+ {#if !inline}
508
+ <div class="relative">
509
+ <input
510
+ bind:this={inputElement}
511
+ type="text"
512
+ class={input({ color, class: clsx(theme?.input, inputClass) })}
513
+ {placeholder}
514
+ value={range && rangeFrom
515
+ ? `${formatDate(rangeFrom)} - ${formatDate(rangeTo)}`
516
+ : formatDate(value)}
517
+ onfocus={() => (isOpen = true)}
518
+ onchange={handleInputChangeWithDateFns}
519
+ onkeydown={handleInputKeydown}
520
+ {disabled}
521
+ {required}
522
+ {inputmode}
523
+ aria-haspopup="dialog"
524
+ {name}
525
+ />
526
+ <button
527
+ type="button"
528
+ class={button({ class: clsx(btnClass, theme?.button, classes?.button) })}
529
+ onclick={() => (isOpen = !isOpen)}
530
+ {disabled}
531
+ aria-label={isOpen ? 'Close date picker' : 'Open date picker'}
532
+ >
533
+ <svg
534
+ class="h-4 w-4 text-gray-500 dark:text-gray-400"
535
+ aria-hidden="true"
536
+ xmlns="http://www.w3.org/2000/svg"
537
+ fill="currentColor"
538
+ viewBox="0 0 20 20"
539
+ >
540
+ <path
541
+ d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"
542
+ ></path>
543
+ </svg>
544
+ </button>
545
+ </div>
546
+ {/if}
547
+
548
+ {#if isOpen || inline}
549
+ <div
550
+ bind:this={calendarRef}
551
+ id="datepicker-dropdown"
552
+ class={base({ inline, class: clsx(theme?.base, className) })}
553
+ transition:fade={{ duration: 100 }}
554
+ role="dialog"
555
+ aria-label="Calendar"
556
+ >
557
+ {#if title}
558
+ <h2 class={titleVariant({ class: clsx(theme?.titleVariant, classes?.titleVariant) })}>
559
+ {title}
560
+ </h2>
561
+ {/if}
562
+
563
+ {#if showMonthSelector}
564
+ <!-- Month/Year Selector View -->
565
+ <div class={nav({ class: clsx(theme?.nav, classes?.nav) })}>
566
+ {@render yearNavButton(false)}
567
+ <h3 class={polite({ class: clsx(theme?.polite, classes?.polite) })} aria-live="polite">
568
+ {currentMonth.getFullYear()}
569
+ </h3>
570
+ {@render yearNavButton(true)}
571
+ </div>
572
+ <div class="grid grid-cols-4 gap-2 p-4">
573
+ {#each monthNames as month, index}
574
+ <Button
575
+ type="button"
576
+ color={monthColor}
577
+ class={monthButton({
578
+ class: clsx(
579
+ currentMonth.getMonth() === index ? monthBtnSelected : monthBtn,
580
+ classes?.monthButton,
581
+ theme?.monthButton
582
+ )
583
+ })}
584
+ onclick={(event: MouseEvent) => selectMonth(index, event)}
585
+ >
586
+ {month}
587
+ </Button>
588
+ {/each}
589
+ </div>
590
+ {:else}
591
+ <div class={nav({ class: clsx(classes?.nav) })}>
592
+ {@render navButton(false)}
593
+ <Button
594
+ type="button"
595
+ class={polite({
596
+ class: clsx(
597
+ 'cursor-pointer rounded px-2 py-1 hover:bg-gray-100 dark:hover:bg-gray-700',
598
+ classes?.polite
599
+ )
600
+ })}
601
+ aria-live="polite"
602
+ onclick={(event: MouseEvent) => toggleMonthSelector(event)}
603
+ >
604
+ {currentMonth.toLocaleString(translationLocale, { month: 'long', year: 'numeric' })}
605
+ </Button>
606
+ {@render navButton(true)}
607
+ </div>
608
+ <div class={grid({ class: clsx(theme?.grid, classes?.grid) })} role="grid">
609
+ {#each weekdays as day}
610
+ <div
611
+ class={columnHeader({ class: clsx(theme?.columnHeader, classes?.columnHeader) })}
612
+ role="columnheader"
613
+ >
614
+ {day}
615
+ </div>
616
+ {/each}
617
+ {#each daysInMonth as day}
618
+ {@const current = day.getMonth() !== currentMonth.getMonth()}
619
+ {@const available = isDateAvailable(day)}
620
+ <Button
621
+ type="button"
622
+ color={isSelected(day) ? color : 'alternative'}
623
+ class={dayButton({
624
+ current,
625
+ today: isToday(day),
626
+ color: isInRange(day) ? color : undefined,
627
+ unavailable: !available,
628
+ class: clsx(
629
+ theme?.dayButton,
630
+ classes?.dayButton,
631
+ !available && 'cursor-not-allowed opacity-50'
632
+ )
633
+ })}
634
+ onclick={() => handleDaySelect(day)}
635
+ onkeydown={handleCalendarKeydown}
636
+ aria-label={day.toLocaleDateString(translationLocale, {
637
+ weekday: 'long',
638
+ year: 'numeric',
639
+ month: 'long',
640
+ day: 'numeric'
641
+ })}
642
+ aria-selected={isSelected(day)}
643
+ aria-disabled={!available}
644
+ disabled={!available}
645
+ role="gridcell"
646
+ >
647
+ {day.getDate()}
648
+ </Button>
649
+ {/each}
650
+ </div>
651
+ {/if}
652
+
653
+ {#if showActionButtons && !showMonthSelector}
654
+ <div class={actionButtons({ class: clsx(theme?.actionButtons, classes?.actionButtons) })}>
655
+ <Button
656
+ onclick={() => handleDaySelect(new Date())}
657
+ {color}
658
+ size="sm"
659
+ disabled={!isDateAvailable(new Date())}>Today</Button
660
+ >
661
+ <Button onclick={handleClear} color="red" size="sm">Clear</Button>
662
+ <Button onclick={handleApply} {color} size="sm">Apply</Button>
663
+ </div>
664
+ {/if}
665
+ </div>
666
+ {/if}
667
+ </div>
668
+
669
+ <!--
670
+ @component
671
+ [Go to docs](https://flowbite-svelte.com/)
672
+ ## Type
673
+ [DatepickerProps](https://github.com/themesberg/flowbite-svelte/blob/main/src/lib/types.ts#L449)
674
+ ## Props
675
+ @prop value = $bindable()
676
+ @prop defaultDate = null
677
+ @prop range = false
678
+ @prop rangeFrom = $bindable()
679
+ @prop rangeTo = $bindable()
680
+ @prop availableFrom = null
681
+ @prop availableTo = null
682
+ @prop locale = "default"
683
+ @prop translationLocale = locale
684
+ @prop firstDayOfWeek = 0
685
+ @prop dateFormat
686
+ @prop placeholder = "Select date"
687
+ @prop disabled = false
688
+ @prop required = false
689
+ @prop inputClass = ""
690
+ @prop color = "primary"
691
+ @prop inline = false
692
+ @prop autohide = true
693
+ @prop showActionButtons = false
694
+ @prop title = ""
695
+ @prop onselect
696
+ @prop onclear
697
+ @prop onapply
698
+ @prop btnClass
699
+ @prop inputmode = "none"
700
+ @prop classes
701
+ @prop monthColor = "alternative"
702
+ @prop monthBtnSelected = "bg-primary-500 text-white"
703
+ @prop monthBtn = "text-gray-700 dark:text-gray-300"
704
+ @prop class: className
705
+ @prop elementRef = $bindable()
706
+ -->