@coreui/react-pro 5.17.1 → 5.18.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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/autocomplete/CAutocomplete.d.ts +229 -0
  3. package/dist/cjs/components/autocomplete/CAutocomplete.js +293 -0
  4. package/dist/cjs/components/autocomplete/CAutocomplete.js.map +1 -0
  5. package/dist/cjs/components/autocomplete/CAutocompleteOptions.d.ts +17 -0
  6. package/dist/cjs/components/autocomplete/CAutocompleteOptions.js +72 -0
  7. package/dist/cjs/components/autocomplete/CAutocompleteOptions.js.map +1 -0
  8. package/dist/cjs/components/autocomplete/index.d.ts +2 -0
  9. package/dist/cjs/components/autocomplete/types.d.ts +18 -0
  10. package/dist/cjs/components/autocomplete/utils.d.ts +11 -0
  11. package/dist/cjs/components/autocomplete/utils.js +107 -0
  12. package/dist/cjs/components/autocomplete/utils.js.map +1 -0
  13. package/dist/cjs/components/calendar/CCalendarPanel.js +6 -7
  14. package/dist/cjs/components/calendar/CCalendarPanel.js.map +1 -1
  15. package/dist/cjs/components/calendar/utils.d.ts +30 -5
  16. package/dist/cjs/components/calendar/utils.js +20 -9
  17. package/dist/cjs/components/calendar/utils.js.map +1 -1
  18. package/dist/cjs/components/date-range-picker/CDateRangePicker.js +10 -8
  19. package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
  20. package/dist/cjs/components/index.d.ts +1 -0
  21. package/dist/cjs/components/time-picker/CTimePicker.js +15 -1
  22. package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
  23. package/dist/cjs/index.js +2 -0
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/utils/getNextSibling.d.ts +2 -0
  26. package/dist/cjs/utils/getNextSibling.js +20 -0
  27. package/dist/cjs/utils/getNextSibling.js.map +1 -0
  28. package/dist/cjs/utils/getPreviousSibling.d.ts +2 -0
  29. package/dist/cjs/utils/getPreviousSibling.js +19 -0
  30. package/dist/cjs/utils/getPreviousSibling.js.map +1 -0
  31. package/dist/cjs/utils/index.d.ts +3 -1
  32. package/dist/esm/components/autocomplete/CAutocomplete.d.ts +229 -0
  33. package/dist/esm/components/autocomplete/CAutocomplete.js +291 -0
  34. package/dist/esm/components/autocomplete/CAutocomplete.js.map +1 -0
  35. package/dist/esm/components/autocomplete/CAutocompleteOptions.d.ts +17 -0
  36. package/dist/esm/components/autocomplete/CAutocompleteOptions.js +70 -0
  37. package/dist/esm/components/autocomplete/CAutocompleteOptions.js.map +1 -0
  38. package/dist/esm/components/autocomplete/index.d.ts +2 -0
  39. package/dist/esm/components/autocomplete/types.d.ts +18 -0
  40. package/dist/esm/components/autocomplete/utils.d.ts +11 -0
  41. package/dist/esm/components/autocomplete/utils.js +96 -0
  42. package/dist/esm/components/autocomplete/utils.js.map +1 -0
  43. package/dist/esm/components/calendar/CCalendarPanel.js +7 -8
  44. package/dist/esm/components/calendar/CCalendarPanel.js.map +1 -1
  45. package/dist/esm/components/calendar/utils.d.ts +30 -5
  46. package/dist/esm/components/calendar/utils.js +20 -9
  47. package/dist/esm/components/calendar/utils.js.map +1 -1
  48. package/dist/esm/components/date-range-picker/CDateRangePicker.js +10 -8
  49. package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
  50. package/dist/esm/components/index.d.ts +1 -0
  51. package/dist/esm/components/time-picker/CTimePicker.js +15 -1
  52. package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
  53. package/dist/esm/index.js +1 -0
  54. package/dist/esm/index.js.map +1 -1
  55. package/dist/esm/utils/getNextSibling.d.ts +2 -0
  56. package/dist/esm/utils/getNextSibling.js +15 -0
  57. package/dist/esm/utils/getNextSibling.js.map +1 -0
  58. package/dist/esm/utils/getPreviousSibling.d.ts +2 -0
  59. package/dist/esm/utils/getPreviousSibling.js +15 -0
  60. package/dist/esm/utils/getPreviousSibling.js.map +1 -0
  61. package/dist/esm/utils/index.d.ts +3 -1
  62. package/package.json +6 -6
  63. package/src/components/autocomplete/CAutocomplete.tsx +802 -0
  64. package/src/components/autocomplete/CAutocompleteOptions.tsx +165 -0
  65. package/src/components/autocomplete/__tests__/CAutocomplete.spec.tsx +244 -0
  66. package/src/components/autocomplete/__tests__/CAutocompleteOptions.spec.tsx +210 -0
  67. package/src/components/autocomplete/index.ts +3 -0
  68. package/src/components/autocomplete/types.ts +25 -0
  69. package/src/components/autocomplete/utils.ts +125 -0
  70. package/src/components/calendar/CCalendarPanel.tsx +5 -15
  71. package/src/components/calendar/utils.ts +50 -10
  72. package/src/components/date-range-picker/CDateRangePicker.tsx +11 -7
  73. package/src/components/index.ts +1 -0
  74. package/src/components/time-picker/CTimePicker.tsx +14 -4
  75. package/src/utils/getNextSibling.ts +18 -0
  76. package/src/utils/getPreviousSibling.ts +18 -0
  77. package/src/utils/index.ts +4 -0
@@ -0,0 +1,125 @@
1
+ import type { Option, OptionsGroup, Search } from './types'
2
+
3
+ export const filterOptions = (
4
+ options: (Option | OptionsGroup)[],
5
+ search: string
6
+ ): (Option | OptionsGroup)[] => {
7
+ const result: (Option | OptionsGroup)[] = []
8
+
9
+ options.forEach((item: Option | OptionsGroup) => {
10
+ if (typeof item !== 'string' && 'options' in item && Array.isArray(item.options)) {
11
+ const filteredOptions = filterOptions(item.options, search)
12
+ if (filteredOptions.length > 0) {
13
+ result.push({ ...item, options: filteredOptions })
14
+ }
15
+ } else if (getOptionLabel(item).toLowerCase().includes(search.toLowerCase())) {
16
+ result.push(item)
17
+ }
18
+ })
19
+
20
+ return result
21
+ }
22
+
23
+ export const flattenOptionsArray = (
24
+ options: (Option | OptionsGroup)[]
25
+ ): (Option | OptionsGroup)[] => {
26
+ const optionsList: (Option | OptionsGroup)[] = []
27
+
28
+ for (const option of options) {
29
+ if (typeof option !== 'string' && 'options' in option && Array.isArray(option.options)) {
30
+ optionsList.push(...option.options)
31
+ } else {
32
+ optionsList.push(option)
33
+ }
34
+ }
35
+
36
+ return optionsList
37
+ }
38
+
39
+ export const isExternalSearch = (search: Search | undefined): boolean => {
40
+ return (
41
+ (typeof search === 'string' && search === 'external') ||
42
+ (typeof search === 'object' && search.external === true)
43
+ )
44
+ }
45
+
46
+ export const isGlobalSearch = (search: Search | undefined): boolean => {
47
+ return (
48
+ (typeof search === 'string' && search === 'global') ||
49
+ (typeof search === 'object' && search.global === true)
50
+ )
51
+ }
52
+
53
+ export const getOptionLabel = (option: Option) =>
54
+ typeof option === 'string' ? option : option.label
55
+
56
+ export const highlightSubstring = (string: string, query?: string) => {
57
+ if (query) {
58
+ const regex = new RegExp(query, 'gi')
59
+ return string.replace(regex, (string) => `<strong>${string}</strong>`)
60
+ }
61
+
62
+ return string
63
+ }
64
+
65
+ export const isOptionDisabled = (option: Option | OptionsGroup | string) =>
66
+ typeof option === 'string' ? false : option.disabled
67
+
68
+ export const isOptionSelected = (
69
+ option: Option | OptionsGroup | string,
70
+ selected: Option | null
71
+ ) => {
72
+ if (!selected) {
73
+ return false
74
+ }
75
+
76
+ if (typeof option === 'string' && typeof selected === 'string') {
77
+ return selected === option
78
+ }
79
+
80
+ if (typeof option !== 'string' && typeof selected !== 'string') {
81
+ return selected.label === option.label
82
+ }
83
+
84
+ return false
85
+ }
86
+
87
+ export const getFirstOptionByLabel = (
88
+ value: string,
89
+ options: (Option | OptionsGroup)[]
90
+ ): Option | null => {
91
+ for (const option of options) {
92
+ if (typeof option !== 'string' && 'options' in option && Array.isArray(option.options)) {
93
+ const found = getFirstOptionByLabel(value, option.options)
94
+ if (found) {
95
+ return found
96
+ }
97
+ }
98
+
99
+ if (getOptionLabel(option).toLowerCase() === value.toLowerCase()) {
100
+ return option
101
+ }
102
+ }
103
+
104
+ return null
105
+ }
106
+
107
+ export const getFirstOptionByValue = (
108
+ value: number,
109
+ options: (Option | OptionsGroup)[]
110
+ ): Option | null => {
111
+ for (const option of options) {
112
+ if (typeof option !== 'string' && 'options' in option && Array.isArray(option.options)) {
113
+ const found = getFirstOptionByValue(value, option.options)
114
+ if (found) {
115
+ return found
116
+ }
117
+ }
118
+
119
+ if (typeof option !== 'string' && 'value' in option && option.value === value) {
120
+ return option
121
+ }
122
+ }
123
+
124
+ return null
125
+ }
@@ -2,7 +2,6 @@ import React, { forwardRef, KeyboardEvent, useEffect, useState } from 'react'
2
2
  import classNames from 'classnames'
3
3
 
4
4
  import {
5
- convertToDateObject,
6
5
  createGroupsInArray,
7
6
  getMonthDetails,
8
7
  getMonthsNames,
@@ -120,16 +119,11 @@ export const CCalendarPanel = forwardRef<HTMLTableElement, CCalendarPanelProps>(
120
119
  )}
121
120
  <tbody>
122
121
  {view === 'days' &&
123
- monthDetails.map((week, index) => {
124
- const date = convertToDateObject(
125
- week.weekNumber === 0
126
- ? `${calendarDate.getFullYear()}W53`
127
- : `${calendarDate.getFullYear()}W${week.weekNumber}`,
128
- selectionType
129
- )
122
+ monthDetails.map(({ week, days }, index) => {
123
+ const { date } = days[0]
130
124
  const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
131
125
  const isSelected = isDateSelected(date, startDate, endDate)
132
- const current = week.days.some((day) => day.month === 'current')
126
+ const current = days.some((day) => day.month === 'current')
133
127
  return (
134
128
  <tr
135
129
  className={classNames('calendar-row', {
@@ -154,12 +148,8 @@ export const CCalendarPanel = forwardRef<HTMLTableElement, CCalendarPanelProps>(
154
148
  onMouseLeave: () => onCalendarMouseLeave(),
155
149
  })}
156
150
  >
157
- {showWeekNumber && (
158
- <th className="calendar-cell-week-number">
159
- {week.weekNumber === 0 ? 53 : week.weekNumber}
160
- </th>
161
- )}
162
- {week.days.map(({ date, month }: { date: Date; month: string }, idx: number) => {
151
+ {showWeekNumber && <th className="calendar-cell-week-number">{week.number}</th>}
152
+ {days.map(({ date, month }: { date: Date; month: string }, idx: number) => {
163
153
  const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
164
154
  const isSelected = isDateSelected(date, startDate, endDate)
165
155
  return month === 'current' || showAdjacementDays ? (
@@ -87,7 +87,8 @@ export const getDateBySelectionType = (
87
87
  }
88
88
 
89
89
  if (selectionType === 'week') {
90
- return `${date.getFullYear()}W${getWeekNumber(date)}`
90
+ const { year, weekNumber } = getISOWeekNumberAndYear(date)
91
+ return `${year}W${weekNumber.toString().padStart(2, '0')}`
91
92
  }
92
93
 
93
94
  if (selectionType === 'month') {
@@ -138,6 +139,32 @@ export const getFirstAvailableDateInRange = (
138
139
  return null
139
140
  }
140
141
 
142
+ /**
143
+ * Returns the ISO 8601 week string (e.g., "2026W01") for a given date.
144
+ *
145
+ * The ISO week date system considers:
146
+ * - Weeks starting on Monday
147
+ * - Week 1 is the week with the first Thursday of the year
148
+ *
149
+ * @param {Date} date - The date for which to calculate the ISO week and year.
150
+ * @returns {string} The ISO week string in the format "YYYYWww" (e.g., "2026W01").
151
+ */
152
+ export const getISOWeekString = (date: Date): string => {
153
+ const tempDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
154
+ // Set to nearest Thursday: current date + 4 - current day number (0 is Sunday)
155
+ const day = tempDate.getUTCDay()
156
+ const diff = day <= 4 ? 4 - day : 11 - day
157
+ tempDate.setUTCDate(tempDate.getUTCDate() + diff)
158
+
159
+ const yearStart = new Date(Date.UTC(tempDate.getUTCFullYear(), 0, 1))
160
+ const weekNumber = Math.ceil(((tempDate.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)
161
+
162
+ const isoYear = tempDate.getUTCFullYear()
163
+ const weekString = `W${weekNumber.toString().padStart(2, '0')}`
164
+
165
+ return `${isoYear}${weekString}`
166
+ }
167
+
141
168
  /**
142
169
  * Retrieves an array of month names based on locale and format.
143
170
  * @param locale - The locale string (e.g., 'en-US').
@@ -254,12 +281,20 @@ const getTrailingDays = (
254
281
  }
255
282
 
256
283
  /**
257
- * Calculates the ISO week number for a given date.
258
- * @param date - The date to calculate the week number for.
259
- * @returns The ISO week number.
284
+ * Calculates the ISO 8601 week number and year for a given date.
285
+ *
286
+ * In the ISO 8601 standard:
287
+ * - Weeks start on Monday.
288
+ * - The first week of the year is the one that contains January 4th.
289
+ * - The year of the week may differ from the calendar year (e.g., Dec 29, 2025 is in ISO year 2026).
290
+ *
291
+ * @param {Date} date - The date for which to calculate the ISO week number and year.
292
+ * @returns {{ weekNumber: number, year: number }} An object containing:
293
+ * - `weekNumber`: the ISO week number (1–53),
294
+ * - `year`: the ISO year (may differ from the calendar year of the date).
260
295
  */
261
- export const getWeekNumber = (date: Date): number => {
262
- const tempDate = new Date(date.getTime())
296
+ export const getISOWeekNumberAndYear = (date: Date): { weekNumber: number; year: number } => {
297
+ const tempDate = new Date(date)
263
298
  tempDate.setHours(0, 0, 0, 0)
264
299
 
265
300
  // Thursday in current week decides the year
@@ -270,7 +305,7 @@ export const getWeekNumber = (date: Date): number => {
270
305
  // Calculate full weeks to the date
271
306
  const weekNumber = 1 + Math.round((tempDate.getTime() - week1.getTime()) / 86_400_000 / 7)
272
307
 
273
- return weekNumber
308
+ return { weekNumber, year: tempDate.getFullYear() }
274
309
  }
275
310
 
276
311
  /**
@@ -284,22 +319,27 @@ export const getMonthDetails = (
284
319
  year: number,
285
320
  month: number,
286
321
  firstDayOfWeek: number
287
- ): { weekNumber?: number; days: { date: Date; month: string }[] }[] => {
322
+ ): { week: { number: number; year: number }; days: { date: Date; month: string }[] }[] => {
288
323
  const daysPrevMonth = getLeadingDays(year, month, firstDayOfWeek)
289
324
  const daysThisMonth = getMonthDays(year, month)
290
325
  const daysNextMonth = getTrailingDays(year, month, daysPrevMonth, daysThisMonth)
291
326
  const days = [...daysPrevMonth, ...daysThisMonth, ...daysNextMonth]
292
- const weeks: { weekNumber?: number; days: { date: Date; month: string }[] }[] = []
327
+ const weeks: {
328
+ week: { number: number; year: number }
329
+ days: { date: Date; month: string }[]
330
+ }[] = []
293
331
 
294
332
  days.forEach((day, index) => {
295
333
  if (index % 7 === 0 || weeks.length === 0) {
296
334
  weeks.push({
335
+ week: { number: 0, year: 0 },
297
336
  days: [],
298
337
  })
299
338
  }
300
339
 
301
340
  if ((index + 1) % 7 === 0) {
302
- weeks[weeks.length - 1].weekNumber = getWeekNumber(day.date)
341
+ const { weekNumber, year } = getISOWeekNumberAndYear(day.date)
342
+ weeks[weeks.length - 1].week = { number: weekNumber, year }
303
343
  }
304
344
 
305
345
  weeks[weeks.length - 1].days.push(day)
@@ -401,6 +401,8 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
401
401
  event.stopPropagation()
402
402
  setStartDate(null)
403
403
  setEndDate(null)
404
+ onStartDateChange?.(null)
405
+ onEndDateChange?.(null)
404
406
  }
405
407
 
406
408
  const handleEndDateChange = (date: Date | string | null) => {
@@ -447,15 +449,17 @@ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDate
447
449
  const date = inputDateParse
448
450
  ? inputDateParse(value)
449
451
  : getLocalDateFromString(value, locale, timepicker)
452
+
450
453
  if (date instanceof Date && date.getTime()) {
451
454
  setCalendarDate(date)
452
- if (input === 'start') {
453
- setStartDate(date)
454
- onStartDateChange?.(date)
455
- } else {
456
- setEndDate(date)
457
- onEndDateChange?.(date)
458
- }
455
+ }
456
+
457
+ if (input === 'start') {
458
+ setStartDate(date ?? null)
459
+ onStartDateChange?.(date ?? null)
460
+ } else {
461
+ setEndDate(date ?? null)
462
+ onEndDateChange?.(date ?? null)
459
463
  }
460
464
  }, inputOnChangeDelay)
461
465
 
@@ -1,5 +1,6 @@
1
1
  export * from './accordion'
2
2
  export * from './alert'
3
+ export * from './autocomplete'
3
4
  export * from './avatar'
4
5
  export * from './backdrop'
5
6
  export * from './badge'
@@ -278,10 +278,20 @@ export const CTimePicker = forwardRef<HTMLDivElement | HTMLLIElement, CTimePicke
278
278
  setIsValid(false)
279
279
  }
280
280
 
281
- const handleOnChange = useDebouncedCallback(
282
- (value: string) => isValidTime(value) && setDate(convertTimeToDate(value)),
283
- inputOnChangeDelay
284
- )
281
+ const handleOnChange = useDebouncedCallback((value: string) => {
282
+ if (isValidTime(value)) {
283
+ const date = convertTimeToDate(value)
284
+ setDate(date)
285
+
286
+ if (date) {
287
+ onTimeChange?.(date.toTimeString(), date.toLocaleTimeString(), date)
288
+ } else {
289
+ onTimeChange?.(null)
290
+ }
291
+ } else {
292
+ onTimeChange?.(null)
293
+ }
294
+ }, inputOnChangeDelay)
285
295
 
286
296
  const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | 'toggle', value: string) => {
287
297
  const _date = date || new Date('1970-01-01')
@@ -0,0 +1,18 @@
1
+ export const getNextSibling = (elem: HTMLElement, selector?: string) => {
2
+ // Get the next sibling element
3
+ let sibling = elem.nextElementSibling
4
+
5
+ // If there's no selector, return the first sibling
6
+ if (!selector) return sibling
7
+
8
+ // If the sibling matches our selector, use it
9
+ // If not, jump to the next sibling and continue the loop
10
+ while (sibling) {
11
+ if (sibling.matches(selector)) return sibling
12
+ sibling = sibling.nextElementSibling
13
+ }
14
+
15
+ return
16
+ }
17
+
18
+ export default getNextSibling
@@ -0,0 +1,18 @@
1
+ const getPreviousSibling = (elem: HTMLElement, selector?: string) => {
2
+ // Get the next sibling element
3
+ let sibling = elem.previousElementSibling
4
+
5
+ // If there's no selector, return the first sibling
6
+ if (!selector) return sibling
7
+
8
+ // If the sibling matches our selector, use it
9
+ // If not, jump to the next sibling and continue the loop
10
+ while (sibling) {
11
+ if (sibling.matches(selector)) return sibling
12
+ sibling = sibling.previousElementSibling
13
+ }
14
+
15
+ return
16
+ }
17
+
18
+ export default getPreviousSibling
@@ -1,5 +1,7 @@
1
1
  import executeAfterTransition from './executeAfterTransition'
2
2
  import getNextActiveElement from './getNextActiveElement'
3
+ import getNextSibling from './getNextSibling'
4
+ import getPreviousSibling from './getPreviousSibling'
3
5
  import getRTLPlacement from './getRTLPlacement'
4
6
  import getTransitionDurationFromElement from './getTransitionDurationFromElement'
5
7
  import isInViewport from './isInViewport'
@@ -10,6 +12,8 @@ import mergeClassNames from './mergeClassNames'
10
12
  export {
11
13
  executeAfterTransition,
12
14
  getNextActiveElement,
15
+ getNextSibling,
16
+ getPreviousSibling,
13
17
  getRTLPlacement,
14
18
  getTransitionDurationFromElement,
15
19
  isInViewport,