Skip to content

Commit 0fcc959

Browse files
committed
feat(Calendar, DatePicker, DateRangePicker): allow to select weeks, months, and years
1 parent 01e6f93 commit 0fcc959

File tree

4 files changed

+220
-137
lines changed

4 files changed

+220
-137
lines changed

js/src/calendar.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import EventHandler from './dom/event-handler.js'
1111
import Manipulator from './dom/manipulator.js'
1212
import { defineJQueryPlugin } from './util/index.js'
1313
import {
14+
convertToDateObject,
1415
createGroupsInArray,
1516
getMonthDetails,
1617
getMonthsNames,
@@ -59,6 +60,7 @@ const Default = {
5960
range: true,
6061
selectAdjacementDays: false,
6162
selectEndDate: false,
63+
selectionType: 'day',
6264
showAdjacementDays: true,
6365
showWeekNumber: false,
6466
startDate: null,
@@ -78,6 +80,7 @@ const DefaultType = {
7880
range: 'boolean',
7981
selectAdjacementDays: 'boolean',
8082
selectEndDate: 'boolean',
83+
selectionType: 'string',
8184
showAdjacementDays: 'boolean',
8285
showWeekNumber: 'boolean',
8386
startDate: '(date|string|null)',
@@ -94,13 +97,17 @@ class Calendar extends BaseComponent {
9497
super(element)
9598

9699
this._config = this._getConfig(config)
97-
this._calendarDate = this._config.calendarDate
98-
this._startDate = this._config.startDate
99-
this._endDate = this._config.endDate
100+
this._calendarDate = convertToDateObject(this._config.calendarDate, this._config.selectionType)
101+
this._startDate = convertToDateObject(this._config.startDate, this._config.selectionType)
102+
this._endDate = convertToDateObject(this._config.endDate, this._config.selectionType)
100103
this._hoverDate = null
101104
this._selectEndDate = this._config.selectEndDate
102105
this._view = 'days'
103106

107+
if (this._config.selectionTyp === 'year') {
108+
this._view = 'years'
109+
}
110+
104111
this._createCalendar()
105112
this._addEventListeners()
106113
}
@@ -368,9 +375,7 @@ class Calendar extends BaseComponent {
368375
${this._view === 'days' ? monthDetails.map(week => (
369376
`<tr class="calendar-row">
370377
${this._config.showWeekNumber ?
371-
`<td class="calendar-cell week-number">
372-
<div class="calendar-cell-inner">${week.weekNumber}</div>
373-
</td>` : ''
378+
`<th class="calendar-cell-week-number">${week.weekNumber}</td>` : ''
374379
}
375380
${week.days.map(({ date, month }) => (
376381
month === 'current' || this._config.showAdjacementDays ?

js/src/util/calendar.js

Lines changed: 118 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,63 @@
1-
export const convertToLocalDate = (d, locale, options = {}) => d.toLocaleDateString(locale, options)
1+
export const convertIsoWeekToDate = isoWeek => {
2+
const [year, week] = isoWeek.split(/w/i)
3+
// Get date for 4th of January for year
4+
const date = new Date(Number(year), 0, 4)
5+
// Get previous Monday, add 7 days for each week after first
6+
date.setDate(
7+
// eslint-disable-next-line no-mixed-operators
8+
date.getDate() - (date.getDay() || 7) + 1 + (Number(week) - 1) * 7
9+
)
10+
return date
11+
}
12+
13+
export const convertToDateObject = (date, selectionType) => {
14+
if (date instanceof Date) {
15+
return date
16+
}
17+
18+
if (selectionType === 'week') {
19+
return convertIsoWeekToDate(date)
20+
}
221

3-
export const convertToLocalTime = (d, locale, options = {}) => d.toLocaleTimeString(locale, options)
22+
return new Date(date)
23+
}
24+
25+
export const convertToLocalDate = (d, locale, options = {}) =>
26+
d.toLocaleDateString(locale, options)
27+
28+
export const convertToLocalTime = (d, locale, options = {}) =>
29+
d.toLocaleTimeString(locale, options)
430

531
export const createGroupsInArray = (arr, numberOfGroups) => {
632
const perGroup = Math.ceil(arr.length / numberOfGroups)
7-
// eslint-disable-next-line unicorn/no-new-array
8-
return new Array(numberOfGroups)
9-
.fill('')
10-
.map((_, i) => arr.slice(i * perGroup, (i + 1) * perGroup))
33+
return Array.from({ length: numberOfGroups })
34+
.fill('')
35+
.map((_, i) => arr.slice(i * perGroup, (i + 1) * perGroup))
1136
}
1237

1338
export const getCurrentYear = () => new Date().getFullYear()
1439

1540
export const getCurrentMonth = () => new Date().getMonth()
1641

17-
export const getLocalDateFromString = (string, locale, time) => {
18-
const date = new Date(2013, 11, 31, 17, 19, 22)
19-
let regex = time ? date.toLocaleString(locale) : date.toLocaleDateString(locale)
20-
regex = regex
21-
.replace('2013', '(?<year>[0-9]{2,4})')
22-
.replace('12', '(?<month>[0-9]{1,2})')
23-
.replace('31', '(?<day>[0-9]{1,2})')
24-
if (time) {
25-
regex = regex
26-
.replace('5', '(?<hour>[0-9]{1,2})')
27-
.replace('17', '(?<hour>[0-9]{1,2})')
28-
.replace('19', '(?<minute>[0-9]{1,2})')
29-
.replace('22', '(?<second>[0-9]{1,2})')
30-
.replace('PM', '(?<ampm>[A-Z]{2})')
42+
export const getDateBySelectionType = (date, selectionType) => {
43+
if (date === null) {
44+
return null
3145
}
3246

33-
const rgx = new RegExp(`${regex}`)
34-
const partials = string.match(rgx)
35-
if (partials === null) {
36-
return
47+
if (selectionType === 'week') {
48+
return `${date.getFullYear()}W${getWeekNumber(date)}`
3749
}
3850

39-
const newDate = partials.groups &&
40-
(time ?
41-
new Date(Number(partials.groups.year, 10), Number(partials.groups.month, 10) - 1, Number(partials.groups.day), partials.groups.ampm ?
42-
(partials.groups.ampm === 'PM' ?
43-
Number(partials.groups.hour) + 12 :
44-
Number(partials.groups.hour)) :
45-
Number(partials.groups.hour), Number(partials.groups.minute), Number(partials.groups.second)) :
46-
new Date(Number(partials.groups.year), Number(partials.groups.month) - 1, Number(partials.groups.day)))
47-
return newDate
51+
if (selectionType === 'month') {
52+
const monthNumber = `0${date.getMonth() + 1}`.slice(-2)
53+
return `${date.getFullYear()}-${monthNumber}`
54+
}
55+
56+
if (selectionType === 'year') {
57+
return `${date.getFullYear()}`
58+
}
59+
60+
return date
4861
}
4962

5063
export const getMonthName = (month, locale) => {
@@ -58,6 +71,7 @@ export const getMonthsNames = locale => {
5871
const months = []
5972
const d = new Date()
6073
d.setDate(1)
74+
6175
for (let i = 0; i < 12; i++) {
6276
d.setMonth(i)
6377
months.push(d.toLocaleString(locale, { month: 'short' }))
@@ -84,6 +98,7 @@ const getLeadingDays = (year, month, firstDayOfWeek) => {
8498
const m = d.getMonth()
8599
const firstWeekday = new Date(y, m, 1).getDay()
86100
let leadingDays = 6 - (6 - firstWeekday) - firstDayOfWeek
101+
87102
if (firstDayOfWeek) {
88103
leadingDays = leadingDays < 0 ? 7 + leadingDays : leadingDays
89104
}
@@ -125,16 +140,67 @@ const getTrailingDays = (year, month, leadingDays, monthDays) => {
125140
}
126141

127142
export const getDayNumber = date =>
128-
Math.ceil((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24)
143+
Math.ceil((Number(date) - Number(new Date(date.getFullYear(), 0, 0))) / 1000 / 60 / 60 / 24)
129144

130-
export const getWeekNumber = date => Math.ceil(getDayNumber(date) / 7)
145+
export const getLocalDateFromString = (string, locale, time) => {
146+
const date = new Date(2013, 11, 31, 17, 19, 22)
147+
let regex = time ? date.toLocaleString(locale) : date.toLocaleDateString(locale)
148+
regex = regex
149+
.replace('2013', '(?<year>[0-9]{2,4})')
150+
.replace('12', '(?<month>[0-9]{1,2})')
151+
.replace('31', '(?<day>[0-9]{1,2})')
152+
if (time) {
153+
regex = regex
154+
.replace('5', '(?<hour>[0-9]{1,2})')
155+
.replace('17', '(?<hour>[0-9]{1,2})')
156+
.replace('19', '(?<minute>[0-9]{1,2})')
157+
.replace('22', '(?<second>[0-9]{1,2})')
158+
.replace('PM', '(?<ampm>[A-Z]{2})')
159+
}
160+
161+
const rgx = new RegExp(`${regex}`)
162+
const partials = string.match(rgx)
163+
if (partials === null) {
164+
return
165+
}
166+
167+
const newDate = partials.groups &&
168+
(time ?
169+
new Date(Number(partials.groups.year, 10), Number(partials.groups.month, 10) - 1, Number(partials.groups.day), partials.groups.ampm ?
170+
(partials.groups.ampm === 'PM' ?
171+
Number(partials.groups.hour) + 12 :
172+
Number(partials.groups.hour)) :
173+
Number(partials.groups.hour), Number(partials.groups.minute), Number(partials.groups.second)) :
174+
new Date(Number(partials.groups.year), Number(partials.groups.month) - 1, Number(partials.groups.day)))
175+
return newDate
176+
}
177+
178+
export const getWeekNumber = date => {
179+
const week1 = new Date(date.getFullYear(), 0, 4)
180+
return (
181+
1 +
182+
Math.round(
183+
// eslint-disable-next-line no-mixed-operators
184+
((date.getTime() - week1.getTime()) / 86400000 -
185+
3 +
186+
((week1.getDay() + 6) % 7)) /
187+
7
188+
)
189+
)
190+
}
131191

132192
export const getMonthDetails = (year, month, firstDayOfWeek) => {
133193
const daysPrevMonth = getLeadingDays(year, month, firstDayOfWeek)
134194
const daysThisMonth = getMonthDays(year, month)
135-
const daysNextMonth = getTrailingDays(year, month, daysPrevMonth, daysThisMonth)
195+
const daysNextMonth = getTrailingDays(
196+
year,
197+
month,
198+
daysPrevMonth,
199+
daysThisMonth
200+
)
136201
const days = [...daysPrevMonth, ...daysThisMonth, ...daysNextMonth]
137202
const weeks = []
203+
138204
for (const [index, day] of days.entries()) {
139205
if (index % 7 === 0 || weeks.length === 0) {
140206
weeks.push({
@@ -143,7 +209,7 @@ export const getMonthDetails = (year, month, firstDayOfWeek) => {
143209
}
144210

145211
if ((index + 1) % 7 === 0) {
146-
weeks[weeks.length - 1].weekNumber = Math.ceil(getDayNumber(day.date) / 7)
212+
weeks[weeks.length - 1].weekNumber = getWeekNumber(day.date)
147213
}
148214

149215
weeks[weeks.length - 1].days.push(day)
@@ -156,6 +222,7 @@ export const isDisableDateInRange = (startDate, endDate, dates) => {
156222
if (startDate && endDate) {
157223
const date = new Date(startDate)
158224
let disabled = false
225+
159226
// eslint-disable-next-line no-unmodified-loop-condition
160227
while (date < endDate) {
161228
date.setDate(date.getDate() + 1)
@@ -201,7 +268,9 @@ export const isDateInRange = (date, start, end) => {
201268
}
202269

203270
export const isDateSelected = (date, start, end) => {
204-
return (start && isSameDateAs(start, date)) || (end && isSameDateAs(end, date))
271+
return (
272+
(start && isSameDateAs(start, date)) || (end && isSameDateAs(end, date))
273+
)
205274
}
206275

207276
export const isEndDate = (date, start, end) => {
@@ -211,15 +280,18 @@ export const isEndDate = (date, start, end) => {
211280
export const isLastDayOfMonth = date => {
212281
const test = new Date(date.getTime())
213282
const month = test.getMonth()
283+
214284
test.setDate(test.getDate() + 1)
215285
return test.getMonth() !== month
216286
}
217287

218288
export const isSameDateAs = (date, date2) => {
219289
if (date instanceof Date && date2 instanceof Date) {
220-
return (date.getDate() === date2.getDate() &&
221-
date.getMonth() === date2.getMonth() &&
222-
date.getFullYear() === date2.getFullYear())
290+
return (
291+
date.getDate() === date2.getDate() &&
292+
date.getMonth() === date2.getMonth() &&
293+
date.getFullYear() === date2.getFullYear()
294+
)
223295
}
224296

225297
if (date === null && date2 === null) {
@@ -235,9 +307,11 @@ export const isStartDate = (date, start, end) => {
235307

236308
export const isToday = date => {
237309
const today = new Date()
238-
return (date.getDate() === today.getDate() &&
239-
date.getMonth() === today.getMonth() &&
240-
date.getFullYear() === today.getFullYear())
310+
return (
311+
date.getDate() === today.getDate() &&
312+
date.getMonth() === today.getMonth() &&
313+
date.getFullYear() === today.getFullYear()
314+
)
241315
}
242316

243317
export const isValidDate = date => {

0 commit comments

Comments
 (0)