@coreui/vue-pro 5.13.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.
- package/README.md +1 -1
- package/dist/cjs/components/calendar/CCalendar.js +61 -65
- package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
- package/dist/cjs/components/calendar/utils.d.ts +53 -2
- package/dist/cjs/components/calendar/utils.js +466 -43
- package/dist/cjs/components/calendar/utils.js.map +1 -1
- package/dist/cjs/components/date-picker/CDatePicker.d.ts +19 -0
- package/dist/cjs/components/date-picker/CDatePicker.js +9 -0
- package/dist/cjs/components/date-picker/CDatePicker.js.map +1 -1
- package/dist/cjs/components/date-range-picker/CDateRangePicker.d.ts +19 -0
- package/dist/cjs/components/date-range-picker/CDateRangePicker.js +98 -57
- package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
- package/dist/cjs/components/date-range-picker/utils.d.ts +0 -9
- package/dist/cjs/components/date-range-picker/utils.js +0 -38
- package/dist/cjs/components/date-range-picker/utils.js.map +1 -1
- package/dist/cjs/components/dropdown/CDropdown.js +22 -13
- package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
- package/dist/cjs/components/dropdown/CDropdownToggle.js +7 -1
- package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
- package/dist/cjs/components/focus-trap/CFocusTrap.d.ts +108 -0
- package/dist/cjs/components/focus-trap/CFocusTrap.js +254 -0
- package/dist/cjs/components/focus-trap/CFocusTrap.js.map +1 -0
- package/dist/cjs/components/focus-trap/index.d.ts +6 -0
- package/dist/cjs/components/focus-trap/index.js +13 -0
- package/dist/cjs/components/focus-trap/index.js.map +1 -0
- package/dist/cjs/components/focus-trap/utils.d.ts +28 -0
- package/dist/cjs/components/focus-trap/utils.js +83 -0
- package/dist/cjs/components/focus-trap/utils.js.map +1 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +70 -66
- package/dist/cjs/components/index.js.map +1 -1
- package/dist/cjs/components/modal/CModal.d.ts +2 -2
- package/dist/cjs/components/modal/CModal.js +19 -27
- package/dist/cjs/components/modal/CModal.js.map +1 -1
- package/dist/cjs/components/modal/CModalHeader.js +4 -2
- package/dist/cjs/components/modal/CModalHeader.js.map +1 -1
- package/dist/cjs/components/offcanvas/COffcanvas.js +3 -2
- package/dist/cjs/components/offcanvas/COffcanvas.js.map +1 -1
- package/dist/cjs/components/picker/CPicker.js +3 -2
- package/dist/cjs/components/picker/CPicker.js.map +1 -1
- package/dist/cjs/components/time-picker/CTimePicker.d.ts +1 -1
- package/dist/cjs/components/time-picker/CTimePicker.js +1 -1
- package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/cjs/components/time-picker/utils.d.ts +1 -1
- package/dist/cjs/composables/useDebouncedCallback.d.ts +1 -1
- package/dist/cjs/composables/useDebouncedCallback.js +1 -1
- package/dist/cjs/composables/useDebouncedCallback.js.map +1 -1
- package/dist/cjs/index.js +76 -72
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/calendar/CCalendar.js +61 -65
- package/dist/esm/components/calendar/CCalendar.js.map +1 -1
- package/dist/esm/components/calendar/utils.d.ts +53 -2
- package/dist/esm/components/calendar/utils.js +464 -44
- package/dist/esm/components/calendar/utils.js.map +1 -1
- package/dist/esm/components/date-picker/CDatePicker.d.ts +19 -0
- package/dist/esm/components/date-picker/CDatePicker.js +9 -0
- package/dist/esm/components/date-picker/CDatePicker.js.map +1 -1
- package/dist/esm/components/date-range-picker/CDateRangePicker.d.ts +19 -0
- package/dist/esm/components/date-range-picker/CDateRangePicker.js +98 -57
- package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
- package/dist/esm/components/date-range-picker/utils.d.ts +0 -9
- package/dist/esm/components/date-range-picker/utils.js +1 -38
- package/dist/esm/components/date-range-picker/utils.js.map +1 -1
- package/dist/esm/components/dropdown/CDropdown.js +23 -14
- package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
- package/dist/esm/components/dropdown/CDropdownToggle.js +7 -1
- package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
- package/dist/esm/components/focus-trap/CFocusTrap.d.ts +108 -0
- package/dist/esm/components/focus-trap/CFocusTrap.js +252 -0
- package/dist/esm/components/focus-trap/CFocusTrap.js.map +1 -0
- package/dist/esm/components/focus-trap/index.d.ts +6 -0
- package/dist/esm/components/focus-trap/index.js +10 -0
- package/dist/esm/components/focus-trap/index.js.map +1 -0
- package/dist/esm/components/focus-trap/utils.d.ts +28 -0
- package/dist/esm/components/focus-trap/utils.js +78 -0
- package/dist/esm/components/focus-trap/utils.js.map +1 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/components/modal/CModal.d.ts +2 -2
- package/dist/esm/components/modal/CModal.js +19 -27
- package/dist/esm/components/modal/CModal.js.map +1 -1
- package/dist/esm/components/modal/CModalHeader.js +4 -2
- package/dist/esm/components/modal/CModalHeader.js.map +1 -1
- package/dist/esm/components/offcanvas/COffcanvas.js +3 -2
- package/dist/esm/components/offcanvas/COffcanvas.js.map +1 -1
- package/dist/esm/components/picker/CPicker.js +3 -2
- package/dist/esm/components/picker/CPicker.js.map +1 -1
- package/dist/esm/components/time-picker/CTimePicker.d.ts +1 -1
- package/dist/esm/components/time-picker/CTimePicker.js +1 -1
- package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/esm/components/time-picker/utils.d.ts +1 -1
- package/dist/esm/composables/useDebouncedCallback.d.ts +1 -1
- package/dist/esm/composables/useDebouncedCallback.js +1 -1
- package/dist/esm/composables/useDebouncedCallback.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/src/components/calendar/CCalendar.ts +55 -70
- package/src/components/calendar/utils.ts +595 -47
- package/src/components/date-picker/CDatePicker.ts +9 -0
- package/src/components/date-range-picker/CDateRangePicker.ts +144 -82
- package/src/components/date-range-picker/utils.ts +0 -58
- package/src/components/dropdown/CDropdown.ts +34 -23
- package/src/components/dropdown/CDropdownToggle.ts +8 -2
- package/src/components/focus-trap/CFocusTrap.ts +303 -0
- package/src/components/focus-trap/index.ts +10 -0
- package/src/components/focus-trap/utils.ts +90 -0
- package/src/components/index.ts +1 -0
- package/src/components/modal/CModal.ts +32 -37
- package/src/components/modal/CModalHeader.ts +5 -3
- package/src/components/offcanvas/COffcanvas.ts +40 -36
- package/src/components/picker/CPicker.ts +58 -52
- package/src/components/time-picker/CTimePicker.ts +12 -13
- 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
|
|
11
|
-
|
|
12
|
-
|
|
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 = (
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
806
|
+
const lastWeek = weeks.at(-1)
|
|
807
|
+
if (lastWeek) {
|
|
808
|
+
lastWeek.week = { number: weekNumber, year }
|
|
809
|
+
}
|
|
317
810
|
}
|
|
318
811
|
|
|
319
|
-
weeks
|
|
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
|
|
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 =
|
|
457
|
-
const _min = min ?
|
|
458
|
-
const _max = max ?
|
|
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 (
|
|
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
|
|
473
|
-
const
|
|
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(
|
|
479
|
-
currentDate <=
|
|
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
|
|
521
|
-
const
|
|
522
|
-
const
|
|
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
|
|
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 (
|
|
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
|
|
592
|
-
const
|
|
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(
|
|
598
|
-
currentDate <=
|
|
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
|
|
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
|
+
}
|