@coreui/vue-pro 5.14.0 → 5.15.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 (106) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/calendar/CCalendar.js +61 -65
  3. package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
  4. package/dist/cjs/components/calendar/utils.d.ts +53 -2
  5. package/dist/cjs/components/calendar/utils.js +466 -43
  6. package/dist/cjs/components/calendar/utils.js.map +1 -1
  7. package/dist/cjs/components/date-range-picker/CDateRangePicker.js +86 -57
  8. package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
  9. package/dist/cjs/components/date-range-picker/utils.d.ts +0 -9
  10. package/dist/cjs/components/date-range-picker/utils.js +0 -38
  11. package/dist/cjs/components/date-range-picker/utils.js.map +1 -1
  12. package/dist/cjs/components/dropdown/CDropdown.js +22 -13
  13. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  14. package/dist/cjs/components/dropdown/CDropdownToggle.js +7 -1
  15. package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
  16. package/dist/cjs/components/focus-trap/CFocusTrap.d.ts +108 -0
  17. package/dist/cjs/components/focus-trap/CFocusTrap.js +254 -0
  18. package/dist/cjs/components/focus-trap/CFocusTrap.js.map +1 -0
  19. package/dist/cjs/components/focus-trap/index.d.ts +6 -0
  20. package/dist/cjs/components/focus-trap/index.js +13 -0
  21. package/dist/cjs/components/focus-trap/index.js.map +1 -0
  22. package/dist/cjs/components/focus-trap/utils.d.ts +28 -0
  23. package/dist/cjs/components/focus-trap/utils.js +83 -0
  24. package/dist/cjs/components/focus-trap/utils.js.map +1 -0
  25. package/dist/cjs/components/index.d.ts +1 -0
  26. package/dist/cjs/components/index.js +70 -66
  27. package/dist/cjs/components/index.js.map +1 -1
  28. package/dist/cjs/components/modal/CModal.d.ts +2 -2
  29. package/dist/cjs/components/modal/CModal.js +19 -27
  30. package/dist/cjs/components/modal/CModal.js.map +1 -1
  31. package/dist/cjs/components/modal/CModalHeader.js +4 -2
  32. package/dist/cjs/components/modal/CModalHeader.js.map +1 -1
  33. package/dist/cjs/components/offcanvas/COffcanvas.js +3 -2
  34. package/dist/cjs/components/offcanvas/COffcanvas.js.map +1 -1
  35. package/dist/cjs/components/picker/CPicker.js +3 -2
  36. package/dist/cjs/components/picker/CPicker.js.map +1 -1
  37. package/dist/cjs/components/time-picker/CTimePicker.d.ts +1 -1
  38. package/dist/cjs/components/time-picker/CTimePicker.js +1 -1
  39. package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
  40. package/dist/cjs/components/time-picker/utils.d.ts +1 -1
  41. package/dist/cjs/composables/useDebouncedCallback.d.ts +1 -1
  42. package/dist/cjs/composables/useDebouncedCallback.js +1 -1
  43. package/dist/cjs/composables/useDebouncedCallback.js.map +1 -1
  44. package/dist/cjs/index.js +76 -72
  45. package/dist/cjs/index.js.map +1 -1
  46. package/dist/esm/components/calendar/CCalendar.js +61 -65
  47. package/dist/esm/components/calendar/CCalendar.js.map +1 -1
  48. package/dist/esm/components/calendar/utils.d.ts +53 -2
  49. package/dist/esm/components/calendar/utils.js +464 -44
  50. package/dist/esm/components/calendar/utils.js.map +1 -1
  51. package/dist/esm/components/date-range-picker/CDateRangePicker.js +86 -57
  52. package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
  53. package/dist/esm/components/date-range-picker/utils.d.ts +0 -9
  54. package/dist/esm/components/date-range-picker/utils.js +1 -38
  55. package/dist/esm/components/date-range-picker/utils.js.map +1 -1
  56. package/dist/esm/components/dropdown/CDropdown.js +23 -14
  57. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  58. package/dist/esm/components/dropdown/CDropdownToggle.js +7 -1
  59. package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
  60. package/dist/esm/components/focus-trap/CFocusTrap.d.ts +108 -0
  61. package/dist/esm/components/focus-trap/CFocusTrap.js +252 -0
  62. package/dist/esm/components/focus-trap/CFocusTrap.js.map +1 -0
  63. package/dist/esm/components/focus-trap/index.d.ts +6 -0
  64. package/dist/esm/components/focus-trap/index.js +10 -0
  65. package/dist/esm/components/focus-trap/index.js.map +1 -0
  66. package/dist/esm/components/focus-trap/utils.d.ts +28 -0
  67. package/dist/esm/components/focus-trap/utils.js +78 -0
  68. package/dist/esm/components/focus-trap/utils.js.map +1 -0
  69. package/dist/esm/components/index.d.ts +1 -0
  70. package/dist/esm/components/index.js +2 -0
  71. package/dist/esm/components/index.js.map +1 -1
  72. package/dist/esm/components/modal/CModal.d.ts +2 -2
  73. package/dist/esm/components/modal/CModal.js +19 -27
  74. package/dist/esm/components/modal/CModal.js.map +1 -1
  75. package/dist/esm/components/modal/CModalHeader.js +4 -2
  76. package/dist/esm/components/modal/CModalHeader.js.map +1 -1
  77. package/dist/esm/components/offcanvas/COffcanvas.js +3 -2
  78. package/dist/esm/components/offcanvas/COffcanvas.js.map +1 -1
  79. package/dist/esm/components/picker/CPicker.js +3 -2
  80. package/dist/esm/components/picker/CPicker.js.map +1 -1
  81. package/dist/esm/components/time-picker/CTimePicker.d.ts +1 -1
  82. package/dist/esm/components/time-picker/CTimePicker.js +1 -1
  83. package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
  84. package/dist/esm/components/time-picker/utils.d.ts +1 -1
  85. package/dist/esm/composables/useDebouncedCallback.d.ts +1 -1
  86. package/dist/esm/composables/useDebouncedCallback.js +1 -1
  87. package/dist/esm/composables/useDebouncedCallback.js.map +1 -1
  88. package/dist/esm/index.js +2 -0
  89. package/dist/esm/index.js.map +1 -1
  90. package/package.json +4 -4
  91. package/src/components/calendar/CCalendar.ts +55 -70
  92. package/src/components/calendar/utils.ts +595 -47
  93. package/src/components/date-range-picker/CDateRangePicker.ts +131 -82
  94. package/src/components/date-range-picker/utils.ts +0 -58
  95. package/src/components/dropdown/CDropdown.ts +34 -23
  96. package/src/components/dropdown/CDropdownToggle.ts +8 -2
  97. package/src/components/focus-trap/CFocusTrap.ts +303 -0
  98. package/src/components/focus-trap/index.ts +10 -0
  99. package/src/components/focus-trap/utils.ts +90 -0
  100. package/src/components/index.ts +1 -0
  101. package/src/components/modal/CModal.ts +32 -37
  102. package/src/components/modal/CModalHeader.ts +5 -3
  103. package/src/components/offcanvas/COffcanvas.ts +40 -36
  104. package/src/components/picker/CPicker.ts +58 -52
  105. package/src/components/time-picker/CTimePicker.ts +12 -13
  106. package/src/composables/useDebouncedCallback.ts +1 -1
@@ -1,39 +1,528 @@
1
1
  import type { DisabledDate, SelectionTypes, ViewTypes } from './types'
2
2
 
3
+ // Shared helper types for date parsing
4
+ export type BaseGroups = {
5
+ year: string
6
+ month: string
7
+ day: string
8
+ }
9
+
10
+ export type WeekGroups = {
11
+ year: string
12
+ week: string
13
+ }
14
+
15
+ export type MonthGroups = {
16
+ year: string
17
+ month: string
18
+ }
19
+
20
+ export type YearGroups = {
21
+ year: string
22
+ }
23
+
3
24
  /**
4
25
  * Converts an ISO week string to a Date object representing the Monday of that week.
5
26
  * @param isoWeek - The ISO week string (e.g., "2023W05" or "2023w05").
6
27
  * @returns The Date object for the Monday of the specified week, or null if invalid.
7
28
  */
29
+ /**
30
+ * Helper function to calculate Monday of ISO week 1 for a given year.
31
+ * @param year - The year to calculate for.
32
+ * @returns The Monday of ISO week 1.
33
+ */
34
+ const getMondayOfISOWeek1 = (year: number): Date => {
35
+ const jan4 = new Date(year, 0, 4)
36
+ const jan4DayOfWeek = jan4.getDay()
37
+ const daysFromMonday = jan4DayOfWeek === 0 ? 6 : jan4DayOfWeek - 1 // Sunday = 6 days from Monday
38
+ const mondayOfWeek1 = new Date(jan4)
39
+ mondayOfWeek1.setDate(jan4.getDate() - daysFromMonday)
40
+ return mondayOfWeek1
41
+ }
42
+
43
+ /**
44
+ * Helper function to calculate Monday of a specific ISO week.
45
+ * @param year - The year.
46
+ * @param week - The ISO week number.
47
+ * @returns The Monday of the specified ISO week.
48
+ */
49
+ const getMondayOfISOWeek = (year: number, week: number): Date => {
50
+ const mondayOfWeek1 = getMondayOfISOWeek1(year)
51
+ const weekStart = new Date(mondayOfWeek1)
52
+ // prettier-ignore
53
+ weekStart.setDate(mondayOfWeek1.getDate() + ((week - 1) * 7))
54
+ return weekStart
55
+ }
56
+
57
+ /**
58
+ * Helper function to convert a date to a month number for comparison.
59
+ * @param date - The date to convert.
60
+ * @returns A number representing year*12 + month for easy comparison.
61
+ */
62
+ const dateToMonthNumber = (date: Date): number => {
63
+ // prettier-ignore
64
+ return (date.getFullYear() * 12) + date.getMonth()
65
+ }
66
+
67
+ /**
68
+ * Helper function to check if a value is within min/max range.
69
+ * @param value - The value to check.
70
+ * @param min - Minimum allowed value (null means no minimum).
71
+ * @param max - Maximum allowed value (null means no maximum).
72
+ * @returns True if the value is outside the range, false if within range.
73
+ */
74
+ const isOutsideRange = (value: number, min: number | null, max: number | null): boolean => {
75
+ if (min !== null && value < min) {
76
+ return true
77
+ }
78
+ if (max !== null && value > max) {
79
+ return true
80
+ }
81
+ return false
82
+ }
83
+
84
+ /**
85
+ * Converts an ISO week string to a Date object representing the Monday of that week.
86
+ * @param isoWeek - The ISO week string (e.g., "2023W05" or "2023w05").
87
+ * @returns The Date object for the Monday of the specified week.
88
+ */
8
89
  export const convertIsoWeekToDate = (isoWeek: string): Date => {
9
90
  const [year, week] = isoWeek.split(/[Ww]/)
10
- const date = new Date(Number(year), 0, 4) // 4th Jan is always in week 1
11
- date.setDate(date.getDate() - (date.getDay() || 7) + 1 + (Number(week) - 1) * 7)
12
- return date
91
+ const parsedYear = parseYearSmart(year)
92
+ const parsedWeek = Number.parseInt(week, 10)
93
+
94
+ // Create date from ISO week using helper function
95
+ return getMondayOfISOWeek(parsedYear, parsedWeek)
96
+ }
97
+
98
+ /**
99
+ * Parses a week string and returns a Date object for the Monday of that week.
100
+ * @param dateString - The week string to parse.
101
+ * @returns The Date object for the Monday of the week, or null if invalid.
102
+ */
103
+ const parseWeekString = (dateString: string): Date | null => {
104
+ const weekPatterns = [
105
+ /^(\d{4})-W(\d{1,2})$/, // 2023-W05, 2023-W5
106
+ /^(\d{4})W(\d{1,2})$/, // 2023W05, 2023W5
107
+ /^(\d{4})\s+W(\d{1,2})$/, // 2023 W05, 2023 W5
108
+ ]
109
+
110
+ for (const pattern of weekPatterns) {
111
+ const match = dateString.trim().match(pattern)
112
+ if (match) {
113
+ const parsedYear = parseYearSmart(match[1])
114
+ const parsedWeek = Number.parseInt(match[2], 10)
115
+
116
+ // Create date from ISO week using helper function
117
+ return getMondayOfISOWeek(parsedYear, parsedWeek)
118
+ }
119
+ }
120
+
121
+ // Fallback to existing ISO week parsing
122
+ return convertIsoWeekToDate(dateString)
123
+ }
124
+
125
+ /**
126
+ * Parses a month string and returns a Date object for the first day of that month.
127
+ * @param dateString - The month string to parse.
128
+ * @returns The Date object for the first day of the month, or null if invalid.
129
+ */
130
+ const parseMonthString = (dateString: string): Date | null => {
131
+ const monthPatterns = [
132
+ /^(\d{2,4})[-/.\s](\d{1,2})$/, // 2023-12, 23-12, 2023/12, 23/12, 2023 12, etc.
133
+ /^(\d{1,2})[-/.\s](\d{2,4})$/, // 12-2023, 12-23, 12/2023, 12/23, 12 2023, etc.
134
+ ]
135
+
136
+ for (const pattern of monthPatterns) {
137
+ const match = dateString.trim().match(pattern)
138
+ if (match) {
139
+ const firstGroup = match[1]
140
+ const secondGroup = match[2]
141
+
142
+ // Determine which group is year and which is month
143
+ const parsedFirst = Number.parseInt(firstGroup, 10)
144
+ const parsedSecond = Number.parseInt(secondGroup, 10)
145
+
146
+ let parsedYear: number
147
+ let parsedMonth: number
148
+
149
+ // Determine which group is year and which is month based on several heuristics
150
+ if (firstGroup.length >= 3 || parsedFirst >= 100) {
151
+ // First group is clearly a year (3+ digits or >= 100)
152
+ parsedYear = parseYearSmart(firstGroup)
153
+ parsedMonth = parsedSecond - 1
154
+ } else if (secondGroup.length >= 3 || parsedSecond >= 100) {
155
+ // Second group is clearly a year (3+ digits or >= 100)
156
+ parsedYear = parseYearSmart(secondGroup)
157
+ parsedMonth = parsedFirst - 1
158
+ } else {
159
+ // Both groups are 1-2 digits, use context clues
160
+ // If second group is a valid month (1-12), treat first as year
161
+ if (parsedSecond >= 1 && parsedSecond <= 12 && (parsedFirst > 12 || parsedFirst < 1)) {
162
+ parsedYear = parseYearSmart(firstGroup)
163
+ parsedMonth = parsedSecond - 1
164
+ } else {
165
+ // Default: treat second group as year
166
+ parsedYear = parseYearSmart(secondGroup)
167
+ parsedMonth = parsedFirst - 1
168
+ }
169
+ }
170
+
171
+ if (parsedMonth >= 0 && parsedMonth <= 11) {
172
+ return new Date(parsedYear, parsedMonth, 1)
173
+ }
174
+ }
175
+ }
176
+
177
+ // For month selection, don't use fallback parsing - return null if no pattern matches
178
+ return null
179
+ }
180
+
181
+ /**
182
+ * Parses a year string or number and returns a Date object for January 1st of that year.
183
+ * @param dateString - The year string or number to parse.
184
+ * @returns The Date object for January 1st of the year, or null if invalid.
185
+ */
186
+ const parseYearString = (dateString: string | number): Date | null => {
187
+ const yearString = String(dateString)
188
+ const yearPattern = /^(\d{2,4})$/
189
+ const match = yearString.trim().match(yearPattern)
190
+
191
+ if (match) {
192
+ const groups: YearGroups = { year: match[1] }
193
+ return createDateFromYear(groups)
194
+ }
195
+
196
+ return parseLocalDateString(yearString)
197
+ }
198
+
199
+ /**
200
+ * Helper function to generate multiple date format patterns based on locale.
201
+ * @param locale - The locale to use for date format patterns.
202
+ * @param includeTime - Whether to include time in the patterns.
203
+ * @returns Array of date format patterns.
204
+ */
205
+ const generateDatePatterns = (locale: string, includeTime: boolean): string[] => {
206
+ const referenceDate = new Date(2013, 11, 31, 17, 19, 22)
207
+ const patterns = []
208
+
209
+ try {
210
+ // Get the standard locale format
211
+ const standardFormat = includeTime
212
+ ? referenceDate.toLocaleString(locale)
213
+ : referenceDate.toLocaleDateString(locale)
214
+
215
+ patterns.push(standardFormat)
216
+ } catch {
217
+ // Fallback to default locale if invalid locale provided
218
+ const standardFormat = includeTime
219
+ ? referenceDate.toLocaleString('en-US')
220
+ : referenceDate.toLocaleDateString('en-US')
221
+ patterns.push(standardFormat)
222
+ }
223
+
224
+ // Generate common alternative formats by replacing separators
225
+ const separators = ['/', '-', '.', ' ']
226
+ const standardFormat = patterns[0]
227
+
228
+ // Detect the original separator
229
+ let originalSeparator = '/' // default
230
+ if (standardFormat.includes('/')) {
231
+ originalSeparator = '/'
232
+ } else if (standardFormat.includes('-')) {
233
+ originalSeparator = '-'
234
+ } else if (standardFormat.includes('.')) {
235
+ originalSeparator = '.'
236
+ }
237
+
238
+ for (const sep of separators) {
239
+ if (sep !== originalSeparator) {
240
+ // Escape the original separator for regex if it's a special character
241
+ const escapedSeparator = originalSeparator.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)
242
+ const altFormat = standardFormat.replaceAll(new RegExp(escapedSeparator, 'g'), sep)
243
+ patterns.push(altFormat)
244
+ }
245
+ }
246
+
247
+ return patterns
248
+ }
249
+
250
+ /**
251
+ * Helper function to build regex pattern for date parsing.
252
+ * @param formatString - The date format string.
253
+ * @param includeTime - Whether to include time patterns.
254
+ * @returns The regex pattern string.
255
+ */
256
+ const buildDateRegexPattern = (formatString: string, includeTime: boolean): string => {
257
+ // First escape special regex characters
258
+ // eslint-disable-next-line unicorn/prefer-string-raw
259
+ let regexPattern = formatString.replaceAll(/[.*+?^${}()|[\\]\\]/g, '\\$&')
260
+
261
+ // Then replace the date/time components with regex groups
262
+ regexPattern = regexPattern
263
+ .replace('2013', String.raw`(?<year>\d{2,4})`)
264
+ .replace('12', String.raw`(?<month>\d{1,2})`)
265
+ .replace('31', String.raw`(?<day>\d{1,2})`)
266
+
267
+ if (includeTime) {
268
+ regexPattern = regexPattern
269
+ .replaceAll(/17|5/g, String.raw`(?<hour>\d{1,2})`)
270
+ .replace('19', String.raw`(?<minute>\d{1,2})`)
271
+ .replace('22', String.raw`(?<second>\d{1,2})`)
272
+ .replaceAll(/AM|PM/gi, '(?<ampm>[APap][Mm])')
273
+ }
274
+
275
+ return regexPattern
276
+ }
277
+
278
+ type TimeGroups = {
279
+ hour: string
280
+ minute?: string
281
+ second?: string
282
+ ampm?: string
283
+ }
284
+
285
+ type DateOnlyGroups = BaseGroups
286
+ type DateTimeGroups = BaseGroups & TimeGroups
287
+ type AnyGroups = DateOnlyGroups | DateTimeGroups | WeekGroups | MonthGroups | YearGroups
288
+
289
+ /**
290
+ * Helper function to try parsing with multiple patterns.
291
+ * @param dateString - The date string to parse.
292
+ * @param patterns - Array of format patterns to try.
293
+ * @param includeTime - Whether time parsing is included.
294
+ * @returns Parsed groups or null if no match.
295
+ */
296
+ const tryParseWithPatterns = (
297
+ dateString: string,
298
+ patterns: string[],
299
+ includeTime: boolean
300
+ ): AnyGroups | null => {
301
+ for (const pattern of patterns) {
302
+ const regexPattern = buildDateRegexPattern(pattern, includeTime)
303
+ const regex = new RegExp(`^${regexPattern}$`)
304
+ const match = dateString.trim().match(regex)
305
+
306
+ if (match?.groups) {
307
+ return match.groups as AnyGroups
308
+ }
309
+ }
310
+
311
+ return null
312
+ }
313
+
314
+ /**
315
+ * Helper function to convert 12-hour to 24-hour format.
316
+ * @param hour - Hour string.
317
+ * @param ampm - AM/PM indicator.
318
+ * @returns Hour in 24-hour format.
319
+ */
320
+ const convertTo24Hour = (hour: string, ampm?: string): number => {
321
+ const parsedHour = Number.parseInt(hour, 10)
322
+
323
+ if (!ampm) {
324
+ return parsedHour
325
+ }
326
+
327
+ const isPM = ampm.toLowerCase() === 'pm'
328
+
329
+ if (isPM && parsedHour !== 12) {
330
+ return parsedHour + 12
331
+ }
332
+
333
+ if (!isPM && parsedHour === 12) {
334
+ return 0
335
+ }
336
+
337
+ return parsedHour
338
+ }
339
+
340
+ /**
341
+ * Helper function to validate time components.
342
+ * @param hour - Hour value.
343
+ * @param minute - Minute value.
344
+ * @param second - Second value.
345
+ * @returns True if time components are valid.
346
+ */
347
+ const validateTimeComponents = (hour: number, minute: number, second: number): boolean => {
348
+ return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 && second >= 0 && second <= 59
349
+ }
350
+
351
+ /**
352
+ * Helper function to validate date components.
353
+ * @param month - Month string.
354
+ * @param day - Day string.
355
+ * @returns True if date components are valid.
356
+ */
357
+ const validateDateComponents = (month: string, day: string): boolean => {
358
+ const parsedMonth = Number.parseInt(month, 10) - 1
359
+ const parsedDay = Number.parseInt(day, 10)
360
+
361
+ return parsedMonth >= 0 && parsedMonth <= 11 && parsedDay >= 1 && parsedDay <= 31
362
+ }
363
+
364
+ /**
365
+ * Helper function to create date with time.
366
+ * @param groups - Parsed date and time groups.
367
+ * @returns Date object or null if invalid.
368
+ */
369
+ const createDateWithTime = (groups: DateTimeGroups): Date | null => {
370
+ const { year, month, day, hour, minute, second, ampm } = groups
371
+
372
+ const parsedYear = parseYearSmart(year)
373
+ const parsedMonth = Number.parseInt(month, 10) - 1
374
+ const parsedDay = Number.parseInt(day, 10)
375
+ const parsedHour = convertTo24Hour(hour, ampm)
376
+ const parsedMinute = Number.parseInt(minute ?? '0', 10) || 0
377
+ const parsedSecond = Number.parseInt(second ?? '0', 10) || 0
378
+
379
+ if (!validateTimeComponents(parsedHour, parsedMinute, parsedSecond)) {
380
+ return null
381
+ }
382
+
383
+ return new Date(parsedYear, parsedMonth, parsedDay, parsedHour, parsedMinute, parsedSecond)
384
+ }
385
+
386
+ /**
387
+ * Helper function to create date without time.
388
+ * @param groups - Parsed date groups.
389
+ * @returns Date object or null if invalid.
390
+ */
391
+ const createDateOnly = (groups: DateOnlyGroups): Date | null => {
392
+ const { year, month, day } = groups
393
+
394
+ if (!validateDateComponents(month, day)) {
395
+ return null
396
+ }
397
+
398
+ const parsedYear = parseYearSmart(year)
399
+ const parsedMonth = Number.parseInt(month, 10) - 1
400
+ const parsedDay = Number.parseInt(day, 10)
401
+
402
+ return new Date(parsedYear, parsedMonth, parsedDay)
403
+ }
404
+
405
+ /**
406
+ * Enhanced day parsing with locale-aware patterns.
407
+ * @param dateString - The day string to parse.
408
+ * @param locale - The locale to use for parsing.
409
+ * @param includeTime - Whether to include time parsing.
410
+ * @returns Date object or null if invalid.
411
+ */
412
+ const parseDayString = (dateString: string, locale: string, includeTime: boolean): Date | null => {
413
+ const patterns = generateDatePatterns(locale, includeTime)
414
+ const groups = tryParseWithPatterns(dateString, patterns, includeTime)
415
+
416
+ if (!groups) {
417
+ // Check if input looks like a complete date (has separators and multiple parts)
418
+ // If so, use fallback parsing for formats like "2022/08/17", "2022-08-17"
419
+ // If not (like "1", "12"), return null
420
+ const trimmed = dateString.trim()
421
+ const hasDateSeparators = /[-/.:]/.test(trimmed)
422
+ const parts = trimmed.split(/[-/.\s:]+/).filter((part) => part.length > 0)
423
+ const hasMultipleParts = parts.length >= 2
424
+
425
+ if (hasDateSeparators && hasMultipleParts) {
426
+ // Use fallback for complete date strings that don't match locale patterns
427
+ return parseLocalDateString(dateString)
428
+ }
429
+
430
+ // For incomplete input like "1" or "12", return null
431
+ return null
432
+ }
433
+
434
+ // For day selection, require at least month and day to be present
435
+ if ('month' in groups && 'day' in groups) {
436
+ const { month, day } = groups
437
+ if (!validateDateComponents(month, day)) {
438
+ return null
439
+ }
440
+ } else {
441
+ // If incomplete date information, return null instead of guessing
442
+ return null
443
+ }
444
+
445
+ // Create and return appropriate date object
446
+ return includeTime
447
+ ? createDateWithTime(groups as DateTimeGroups)
448
+ : createDateOnly(groups as DateOnlyGroups)
449
+ }
450
+
451
+ /**
452
+ * Parses a date string into a local Date object.
453
+ * @param dateString - The date string to parse.
454
+ * @returns The Date object in local timezone, or null if invalid.
455
+ */
456
+ const parseLocalDateString = (dateString: string): Date | null => {
457
+ const _date = new Date(Date.parse(dateString))
458
+ if (!Number.isNaN(_date.getTime())) {
459
+ return _date
460
+ }
461
+
462
+ return null
13
463
  }
14
464
 
15
465
  /**
16
466
  * Converts a date string or Date object to a Date object based on selection type.
17
467
  * @param date - The date to convert.
18
468
  * @param selectionType - The type of selection ('day', 'week', 'month', 'year').
469
+ * @param locale - The locale to use for date parsing (for day parsing).
470
+ * @param includeTime - Whether to include time parsing (for day parsing).
19
471
  * @returns The corresponding Date object or null if invalid.
20
472
  */
21
- export const convertToDateObject = (date: Date | string, selectionType?: SelectionTypes): Date => {
473
+ export const convertToDateObject = (
474
+ date: Date | string,
475
+ selectionType?: SelectionTypes,
476
+ locale: string = 'en-US',
477
+ includeTime: boolean = false
478
+ ): Date | null => {
479
+ if (date === null) {
480
+ return null
481
+ }
482
+
22
483
  if (date instanceof Date) {
23
- return date
484
+ return Number.isNaN(date.getTime()) ? null : date
24
485
  }
25
486
 
26
- if (selectionType === 'week') {
27
- return convertIsoWeekToDate(date as string)
487
+ const dateString = date as string
488
+
489
+ switch (selectionType) {
490
+ case 'week': {
491
+ return parseWeekString(dateString)
492
+ }
493
+ case 'month': {
494
+ return parseMonthString(dateString)
495
+ }
496
+ case 'year': {
497
+ return parseYearString(dateString)
498
+ }
499
+ default: {
500
+ // Enhanced day parsing with locale support
501
+ return parseDayString(dateString, locale, includeTime)
502
+ }
28
503
  }
504
+ }
29
505
 
30
- if (selectionType === 'month' || selectionType === 'year') {
31
- const _date = new Date(Date.parse(date))
32
- const userTimezoneOffset = _date.getTimezoneOffset() * 60_000
33
- return new Date(_date.getTime() + userTimezoneOffset)
506
+ /**
507
+ * Enhanced locale-aware date parsing function (replaces getLocalDateFromString).
508
+ * @param dateString - The date string to parse.
509
+ * @param locale - The locale to use for date format patterns.
510
+ * @param includeTime - Whether to include time parsing.
511
+ * @param selectionType - The selection type ('day', 'week', 'month', 'year').
512
+ * @returns A Date object if parsing succeeds, null if parsing fails.
513
+ */
514
+ export const getLocalDateFromString = (
515
+ dateString: string,
516
+ locale: string = 'en-US',
517
+ includeTime: boolean = false,
518
+ selectionType: SelectionTypes = 'day'
519
+ ): Date | null => {
520
+ // Input validation
521
+ if (!dateString || typeof dateString !== 'string') {
522
+ return null
34
523
  }
35
524
 
36
- return new Date(Date.parse(date))
525
+ return convertToDateObject(dateString, selectionType, locale, includeTime)
37
526
  }
38
527
 
39
528
  /**
@@ -66,7 +555,8 @@ export const getCalendarDate = (calendarDate: Date, order: number, view: ViewTyp
66
555
  }
67
556
 
68
557
  if (order !== 0 && view === 'years') {
69
- return new Date(calendarDate.getFullYear() + 12 * order, calendarDate.getMonth(), 1)
558
+ // prettier-ignore
559
+ return new Date(calendarDate.getFullYear() + (12 * order), calendarDate.getMonth(), 1)
70
560
  }
71
561
 
72
562
  return calendarDate
@@ -277,7 +767,7 @@ export const getISOWeekNumberAndYear = (date: Date): { weekNumber: number; year:
277
767
  const week1 = new Date(tempDate.getFullYear(), 0, 4)
278
768
 
279
769
  // Calculate full weeks to the date
280
- const weekNumber = 1 + Math.round((tempDate.getTime() - week1.getTime()) / 86_400_000 / 7)
770
+ const weekNumber = 1 + Math.round((tempDate.getTime() - week1.getTime()) / (86_400_000 * 7))
281
771
 
282
772
  return { weekNumber, year: tempDate.getFullYear() }
283
773
  }
@@ -313,10 +803,16 @@ export const getMonthDetails = (
313
803
 
314
804
  if ((index + 1) % 7 === 0) {
315
805
  const { weekNumber, year } = getISOWeekNumberAndYear(day.date)
316
- weeks[weeks.length - 1].week = { number: weekNumber, year }
806
+ const lastWeek = weeks.at(-1)
807
+ if (lastWeek) {
808
+ lastWeek.week = { number: weekNumber, year }
809
+ }
317
810
  }
318
811
 
319
- weeks[weeks.length - 1].days.push(day)
812
+ const lastWeek = weeks.at(-1)
813
+ if (lastWeek) {
814
+ lastWeek.days.push(day)
815
+ }
320
816
  })
321
817
 
322
818
  return weeks
@@ -387,7 +883,7 @@ export const isDateInRange = (date: Date, start: Date | null, end: Date | null):
387
883
  const _start = start ? removeTimeFromDate(start) : null
388
884
  const _end = end ? removeTimeFromDate(end) : null
389
885
 
390
- return !!(_start && _end && _start <= _date && _date <= _end)
886
+ return Boolean(_start && _end && _start <= _date && _date <= _end)
391
887
  }
392
888
 
393
889
  /**
@@ -453,15 +949,11 @@ export const isMonthDisabled = (
453
949
  max?: Date | null,
454
950
  disabledDates?: DisabledDate | DisabledDate[]
455
951
  ) => {
456
- const current = date.getFullYear() * 12 + date.getMonth()
457
- const _min = min ? min.getFullYear() * 12 + min.getMonth() : null
458
- const _max = max ? max.getFullYear() * 12 + max.getMonth() : null
459
-
460
- if (_min && current < _min) {
461
- return true
462
- }
952
+ const current = dateToMonthNumber(date)
953
+ const _min = min ? dateToMonthNumber(min) : null
954
+ const _max = max ? dateToMonthNumber(max) : null
463
955
 
464
- if (_max && current > _max) {
956
+ if (isOutsideRange(current, _min, _max)) {
465
957
  return true
466
958
  }
467
959
 
@@ -469,14 +961,14 @@ export const isMonthDisabled = (
469
961
  return false
470
962
  }
471
963
 
472
- const start = min ? Math.max(date.getTime(), min.getTime()) : date
473
- const end = max
964
+ const startTime = min ? Math.max(date.getTime(), min.getTime()) : date.getTime()
965
+ const endTime = max
474
966
  ? Math.min(date.getTime(), max.getTime())
475
- : new Date(new Date().getFullYear(), 11, 31)
967
+ : new Date(new Date().getFullYear(), 11, 31).getTime()
476
968
 
477
969
  for (
478
- const currentDate = new Date(start);
479
- currentDate <= end;
970
+ const currentDate = new Date(startTime);
971
+ currentDate.getTime() <= endTime;
480
972
  currentDate.setDate(currentDate.getDate() + 1)
481
973
  ) {
482
974
  if (!isDateDisabled(currentDate, min, max, disabledDates)) {
@@ -517,13 +1009,11 @@ export const isMonthSelected = (date: Date, start: Date | null, end: Date | null
517
1009
  * @returns True if the month is within the range, false otherwise.
518
1010
  */
519
1011
  export const isMonthInRange = (date: Date, start: Date | null, end: Date | null): boolean => {
520
- const year = date.getFullYear()
521
- const month = date.getMonth()
522
- const _start = start ? start.getFullYear() * 12 + start.getMonth() : null
523
- const _end = end ? end.getFullYear() * 12 + end.getMonth() : null
524
- const _date = year * 12 + month
1012
+ const _start = start ? dateToMonthNumber(start) : null
1013
+ const _end = end ? dateToMonthNumber(end) : null
1014
+ const _date = dateToMonthNumber(date)
525
1015
 
526
- return !!(_start && _end && _start <= _date && _date <= _end)
1016
+ return Boolean(_start && _end && _start <= _date && _date <= _end)
527
1017
  }
528
1018
 
529
1019
  /**
@@ -576,11 +1066,7 @@ export const isYearDisabled = (
576
1066
  const minYear = min ? min.getFullYear() : null
577
1067
  const maxYear = max ? max.getFullYear() : null
578
1068
 
579
- if (minYear && year < minYear) {
580
- return true
581
- }
582
-
583
- if (maxYear && year > maxYear) {
1069
+ if (isOutsideRange(year, minYear, maxYear)) {
584
1070
  return true
585
1071
  }
586
1072
 
@@ -588,14 +1074,14 @@ export const isYearDisabled = (
588
1074
  return false
589
1075
  }
590
1076
 
591
- const start = min ? Math.max(date.getTime(), min.getTime()) : date
592
- const end = max
1077
+ const startTime = min ? Math.max(date.getTime(), min.getTime()) : date.getTime()
1078
+ const endTime = max
593
1079
  ? Math.min(date.getTime(), max.getTime())
594
- : new Date(new Date().getFullYear(), 11, 31)
1080
+ : new Date(new Date().getFullYear(), 11, 31).getTime()
595
1081
 
596
1082
  for (
597
- const currentDate = new Date(start);
598
- currentDate <= end;
1083
+ const currentDate = new Date(startTime);
1084
+ currentDate.getTime() <= endTime;
599
1085
  currentDate.setDate(currentDate.getDate() + 1)
600
1086
  ) {
601
1087
  if (!isDateDisabled(currentDate, min, max, disabledDates)) {
@@ -639,7 +1125,7 @@ export const isYearInRange = (date: Date, start: Date | null, end: Date | null):
639
1125
  const _start = start ? start.getFullYear() : null
640
1126
  const _end = end ? end.getFullYear() : null
641
1127
 
642
- return !!(_start && _end && _start <= year && year <= _end)
1128
+ return Boolean(_start && _end && _start <= year && year <= _end)
643
1129
  }
644
1130
 
645
1131
  /**
@@ -679,3 +1165,65 @@ export const setTimeFromDate = (target: Date | null, source: Date | null): Date
679
1165
 
680
1166
  return result
681
1167
  }
1168
+
1169
+ /**
1170
+ * Parses a year string with smart 2-digit handling.
1171
+ * @param yearString - The year string to parse.
1172
+ * @returns The parsed year as a number with intelligent century assignment.
1173
+ */
1174
+ export const parseYearSmart = (yearString: string): number => {
1175
+ let parsedYear = Number.parseInt(yearString, 10)
1176
+
1177
+ // Handle 2-digit years with intelligent century assignment
1178
+ if (parsedYear < 100) {
1179
+ const currentYear = new Date().getFullYear()
1180
+ const currentCentury = Math.floor(currentYear / 100) * 100
1181
+ parsedYear = currentCentury + parsedYear
1182
+
1183
+ // If the result is more than 50 years in the future, use previous century
1184
+ // This creates a sliding window: for current year 2025, years 76-99 become 1976-1999
1185
+ // and years 00-75 become 2000-2075
1186
+ if (parsedYear > currentYear + 50) {
1187
+ parsedYear -= 100
1188
+ }
1189
+ }
1190
+
1191
+ return parsedYear
1192
+ }
1193
+
1194
+ /**
1195
+ * Creates a date from year groups.
1196
+ * @param groups - The year groups containing year string.
1197
+ * @returns A Date object for January 1st of the year.
1198
+ */
1199
+ export const createDateFromYear = (groups: YearGroups): Date => {
1200
+ const { year } = groups
1201
+ const parsedYear = parseYearSmart(year)
1202
+ return new Date(parsedYear, 0, 1)
1203
+ }
1204
+
1205
+ /**
1206
+ * Creates a date from month groups.
1207
+ * @param groups - The month groups containing year and month strings.
1208
+ * @returns A Date object for the first day of the month.
1209
+ */
1210
+ export const createDateFromMonth = (groups: MonthGroups): Date => {
1211
+ const { year, month } = groups
1212
+ const parsedYear = parseYearSmart(year)
1213
+ const parsedMonth = Number.parseInt(month, 10) - 1
1214
+ return new Date(parsedYear, parsedMonth, 1)
1215
+ }
1216
+
1217
+ /**
1218
+ * Creates a date from week groups.
1219
+ * @param groups - The week groups containing year and week strings.
1220
+ * @returns A Date object for the Monday of the specified week.
1221
+ */
1222
+ export const createDateFromWeek = (groups: WeekGroups): Date => {
1223
+ const { year, week } = groups
1224
+ const parsedYear = parseYearSmart(year)
1225
+ const parsedWeek = Number.parseInt(week, 10)
1226
+
1227
+ // Create date from ISO week using helper function
1228
+ return getMondayOfISOWeek(parsedYear, parsedWeek)
1229
+ }