imx-select-datepicker 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -31
- package/index.js +2 -1
- package/jest.config.cjs +4 -0
- package/package.json +6 -2
- package/src/InputGroup/InputGroup.js +17 -3
- package/src/InputGroup/InputGroup.test.js +30 -0
- package/src/InputGroup/utils/DaySelect.js +1 -0
- package/src/InputGroup/utils/MonthSelect.js +11 -0
- package/src/InputRange/InputRange.js +1 -1
- package/src/SelectDatepicker/SelectDatepicker.js +128 -0
- package/src/SelectDatepicker/SelectDatepicker.test.js +160 -0
package/README.md
CHANGED
|
@@ -1,83 +1,263 @@
|
|
|
1
|
-
# imx-select-datepicker
|
|
1
|
+
# imx-select-datepicker
|
|
2
2
|
|
|
3
|
-
A component for
|
|
3
|
+
A component for handling date selection via select elements for day, month, and year. The component can be used as a single date picker or as a range date picker.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## SelectDatepicker
|
|
6
|
+
|
|
7
|
+
A component for managing date inputs with support for single date and date range modes.
|
|
8
|
+
If no date is explicitly set via `setDate()`, the component defaults to the first day of the first month in the available options range.
|
|
9
|
+
|
|
10
|
+
### Constructor
|
|
6
11
|
|
|
7
12
|
```javascript
|
|
8
|
-
|
|
13
|
+
import { SelectDatepicker } from 'imx-select-datepicker';
|
|
14
|
+
|
|
15
|
+
const selectDatepicker = new SelectDatepicker(config);
|
|
9
16
|
```
|
|
10
17
|
|
|
18
|
+
**Parameters:**
|
|
19
|
+
- `config` (Object) - Configuration object
|
|
20
|
+
- `mode` (String) - Mode of the component: `'single'` for single date or `'range'` for date range
|
|
21
|
+
- `availableOptions` (Object, optional)
|
|
22
|
+
- `start` (Date, optional) - start date for the range of the day/month options - works only in monthly precision (defaults to one year ago)
|
|
23
|
+
- `end` (Date, optional) - end date for the range of the day/month options - works only in monthly precision (defaults to one year from now)
|
|
24
|
+
- `onChange` (Function, optional) - Callback function that is called when the date changes
|
|
25
|
+
|
|
26
|
+
### Usage
|
|
27
|
+
|
|
28
|
+
#### Single Date Mode
|
|
29
|
+
|
|
11
30
|
```javascript
|
|
12
|
-
const
|
|
13
|
-
|
|
31
|
+
const selectDatepicker = new SelectDatepicker({
|
|
32
|
+
mode: 'single',
|
|
33
|
+
availableOptions: {
|
|
34
|
+
start: new Date('2026-01-01'),
|
|
35
|
+
end: new Date('2026-12-31')
|
|
36
|
+
},
|
|
37
|
+
onChange: (dates) => {
|
|
38
|
+
console.log('Selected date:', dates);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
14
41
|
|
|
15
|
-
|
|
42
|
+
document.getElementById('datepicker').appendChild(selectDatepicker.getMarkup());
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Date Range Mode
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const selectDatepicker = new SelectDatepicker({
|
|
49
|
+
mode: 'range',
|
|
50
|
+
availableOptions: {
|
|
51
|
+
start: new Date('2026-01-01'),
|
|
52
|
+
end: new Date('2026-12-31')
|
|
53
|
+
},
|
|
54
|
+
onChange: (dates) => {
|
|
55
|
+
console.log('From:', dates[0], 'To:', dates[1]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
document.getElementById('datepicker').appendChild(selectDatepicker.getMarkup());
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Methods
|
|
63
|
+
|
|
64
|
+
#### `getMarkup()`
|
|
65
|
+
Returns the DOM element of the component that can be inserted into the document.
|
|
66
|
+
|
|
67
|
+
**Returns:** DOM Element
|
|
68
|
+
|
|
69
|
+
#### `setDate(date)`
|
|
70
|
+
Sets the date(s) of the component.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
- `date` (Date | Array\<Date\>) - A single date or an array with two dates for the date range
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// Single date
|
|
77
|
+
selectDatepicker.setDate(new Date('2026-06-15'));
|
|
78
|
+
|
|
79
|
+
// Date range
|
|
80
|
+
selectDatepicker.setDate([
|
|
81
|
+
new Date('2026-06-01'),
|
|
82
|
+
new Date('2026-06-30')
|
|
83
|
+
]);
|
|
16
84
|
```
|
|
17
85
|
|
|
86
|
+
#### `setMinDate(date)`
|
|
87
|
+
Sets the minimal selectable (non disabled) date.
|
|
88
|
+
|
|
18
89
|
**Parameters:**
|
|
19
|
-
- `
|
|
20
|
-
|
|
90
|
+
- `date` (Date) - A single date.
|
|
91
|
+
|
|
92
|
+
#### `getDate()`
|
|
93
|
+
Returns the currently selected dates.
|
|
21
94
|
|
|
22
|
-
|
|
95
|
+
**Returns:** Array\<String\> - Array with one or two ISO date strings (YYYY-MM-DD)
|
|
23
96
|
|
|
24
97
|
```javascript
|
|
25
|
-
const
|
|
26
|
-
|
|
98
|
+
const selectedDates = selectDatepicker.getDate();
|
|
99
|
+
console.log(selectedDates); // ['2026-06-01', '2026-06-30'] or ['2026-06-15']
|
|
100
|
+
```
|
|
27
101
|
|
|
28
|
-
|
|
102
|
+
### Events
|
|
103
|
+
|
|
104
|
+
The component automatically calls the `onChange` callback function when the date selection changes:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const selectDatepicker = new SelectDatepicker({
|
|
108
|
+
mode: 'range',
|
|
109
|
+
onChange: (dates) => {
|
|
110
|
+
console.log('New start date:', dates[0]);
|
|
111
|
+
console.log('New end date:', dates[1]);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
29
114
|
```
|
|
30
115
|
|
|
31
|
-
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## InputGroup
|
|
119
|
+
|
|
120
|
+
A component for managing date inputs with separate controls for day, month, and year.
|
|
121
|
+
If no date is explicitly set via `setDate()`, the component defaults to the first day of the month specified by `startDate`.
|
|
122
|
+
|
|
123
|
+
### Constructor
|
|
32
124
|
|
|
33
125
|
```javascript
|
|
34
|
-
|
|
35
|
-
inputDate.appendChild(inputGroupFrom.markup);
|
|
126
|
+
import { InputGroup } from 'imx-select-datepicker';
|
|
36
127
|
|
|
37
|
-
const
|
|
38
|
-
|
|
128
|
+
const startDate = new Date('2026-01-01');
|
|
129
|
+
const endDate = new Date('2026-12-31');
|
|
39
130
|
|
|
40
|
-
const
|
|
131
|
+
const inputGroup = new InputGroup(startDate, endDate);
|
|
132
|
+
|
|
133
|
+
// or with default options
|
|
134
|
+
const inputGroupWithDefaultOptions = new InputGroup();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
- `startDate` (Date, optional) - start date for the range of the day/month options - works only in monthly precision (defaults to one year ago)
|
|
139
|
+
- `endDate` (Date, optional) - end date for the range of the day/month options - works only in monthly precision (defaults to one year from now)
|
|
140
|
+
|
|
141
|
+
### Usage
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
const startDate = new Date('2026-01-01');
|
|
145
|
+
const endDate = new Date('2026-12-31');
|
|
146
|
+
|
|
147
|
+
const inputGroupFrom = new InputGroup(startDate, endDate);
|
|
148
|
+
document.getElementById('input-date').appendChild(inputGroupFrom.getMarkup());
|
|
149
|
+
|
|
150
|
+
const inputGroupTo = new InputGroup(startDate, endDate);
|
|
151
|
+
document.getElementById('input-date').appendChild(inputGroupTo.getMarkup());
|
|
41
152
|
|
|
42
153
|
inputGroupFrom.update(new Date('2026-01-01'));
|
|
43
154
|
inputGroupTo.update(new Date('2026-12-31'));
|
|
44
155
|
```
|
|
45
156
|
|
|
46
|
-
|
|
157
|
+
### Properties
|
|
158
|
+
|
|
159
|
+
- **`daySelect.select`** - Select element for day selection
|
|
160
|
+
- **`monthSelect.select`** - Select element for month selection
|
|
161
|
+
- **`yearSelect.select`** - Select element for year selection
|
|
47
162
|
|
|
48
|
-
|
|
49
|
-
- **`monthSelect.select`** - Select element for month selection, to register a change listener.
|
|
163
|
+
### Methods
|
|
50
164
|
|
|
51
|
-
|
|
52
|
-
### `getMarkup()`
|
|
165
|
+
#### `getMarkup()`
|
|
53
166
|
Returns the HTML markup for the input group to append it to the DOM.
|
|
54
167
|
|
|
55
|
-
|
|
168
|
+
**Returns:** DOM Element
|
|
169
|
+
|
|
170
|
+
#### `getMonthSelect()`
|
|
56
171
|
Returns the month select element for registering change listeners.
|
|
57
172
|
|
|
58
|
-
|
|
173
|
+
**Returns:** HTMLSelectElement
|
|
174
|
+
|
|
175
|
+
#### `getDaySelect()`
|
|
59
176
|
Returns the day select element for registering change listeners.
|
|
60
177
|
|
|
61
|
-
|
|
178
|
+
**Returns:** HTMLSelectElement
|
|
179
|
+
|
|
180
|
+
#### `getValue()`
|
|
62
181
|
Returns the currently selected date as a ISO date string (YYYY-MM-DD).
|
|
63
182
|
|
|
64
|
-
|
|
183
|
+
**Returns:** String
|
|
184
|
+
|
|
185
|
+
#### `update(date)`
|
|
65
186
|
Updates the input fields with a new date.
|
|
66
187
|
|
|
67
188
|
**Parameters:**
|
|
68
189
|
- `date` (Date) - The new date
|
|
69
190
|
|
|
70
|
-
|
|
191
|
+
#### `setMinDate(date)`
|
|
71
192
|
Sets the minimum selectable date for the input fields.
|
|
72
193
|
|
|
73
194
|
**Parameters:**
|
|
74
195
|
- `date` (Date) - The minimum date
|
|
75
196
|
|
|
76
|
-
|
|
197
|
+
### Events
|
|
77
198
|
|
|
78
199
|
```javascript
|
|
79
200
|
inputGroupFrom.getDaySelect().addEventListener('change', (event) => {
|
|
80
|
-
const newDate = new Date(
|
|
201
|
+
const newDate = new Date(inputGroupFrom.getValue());
|
|
81
202
|
console.log('New date:', newDate);
|
|
82
203
|
});
|
|
83
204
|
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
## InputRange
|
|
210
|
+
|
|
211
|
+
A component that connects two InputGroup instances to create a date range picker. The difference to SelectDatepicker is, that you have to handle the InputGroups individually.
|
|
212
|
+
|
|
213
|
+
### Constructor
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
import { InputGroup, InputRange } from 'imx-select-datepicker';
|
|
217
|
+
|
|
218
|
+
const startDate = new Date('2026-01-01');
|
|
219
|
+
const endDate = new Date('2026-12-31');
|
|
220
|
+
|
|
221
|
+
const inputGroupFrom = new InputGroup(startDate, endDate);
|
|
222
|
+
const inputGroupTo = new InputGroup(startDate, endDate);
|
|
223
|
+
|
|
224
|
+
const inputRange = new InputRange(inputGroupFrom, inputGroupTo);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Parameters:**
|
|
228
|
+
- `inputGroupFrom` (InputGroup) - The InputGroup selecting the from date of the range
|
|
229
|
+
- `inputGroupTo` (InputGroup) - The InputGroup selecting the to date of the range
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Usage Examples
|
|
234
|
+
|
|
235
|
+
### Single Date Picker (using InputGroup only)
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
const startDate = new Date('2026-01-01');
|
|
239
|
+
const endDate = new Date('2026-12-31');
|
|
240
|
+
|
|
241
|
+
const inputGroup = new InputGroup(startDate, endDate);
|
|
242
|
+
document.getElementById('datepicker').appendChild(inputGroup.getMarkup());
|
|
243
|
+
|
|
244
|
+
inputGroup.update(new Date('2026-12-01'));
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Range Date Picker
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const startDate = new Date('2026-01-01');
|
|
251
|
+
const endDate = new Date('2026-12-31');
|
|
252
|
+
|
|
253
|
+
const inputGroupFrom = new InputGroup(startDate, endDate);
|
|
254
|
+
document.getElementById('datepicker-from').appendChild(inputGroupFrom.getMarkup());
|
|
255
|
+
|
|
256
|
+
const inputGroupTo = new InputGroup(startDate, endDate);
|
|
257
|
+
document.getElementById('datepicker-to').appendChild(inputGroupTo.getMarkup());
|
|
258
|
+
|
|
259
|
+
const inputRange = new InputRange(inputGroupFrom, inputGroupTo);
|
|
260
|
+
|
|
261
|
+
inputGroupFrom.update(new Date('2026-01-01'));
|
|
262
|
+
inputGroupTo.update(new Date('2026-12-31'));
|
|
263
|
+
```
|
package/index.js
CHANGED
package/jest.config.cjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imx-select-datepicker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A simple keyboard accessible select based datepicker",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -15,9 +15,13 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "index.js",
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "
|
|
18
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"dayjs": "^1.11.19"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"jest": "^30.2.0",
|
|
25
|
+
"jest-environment-jsdom": "^30.2.0"
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -45,11 +45,25 @@ export class InputGroup {
|
|
|
45
45
|
return this.daySelect.select;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
isAvailable(jsDateObject) {
|
|
49
|
+
const dayJsDate = dayjs(jsDateObject);
|
|
50
|
+
|
|
51
|
+
const optionGroup = this.optionGroups.find((optionGroup) => {
|
|
52
|
+
return dayjs(optionGroup.index).isSame(dayJsDate, 'month');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return !!optionGroup;
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
update(jsDateObject) {
|
|
49
59
|
if(dayjs(jsDateObject).isValid()) {
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
if(this.isAvailable(jsDateObject)) {
|
|
61
|
+
this.updateMonth(jsDateObject);
|
|
62
|
+
this.updateDayOptions(jsDateObject);
|
|
63
|
+
this.updateDay(jsDateObject);
|
|
64
|
+
} else {
|
|
65
|
+
console.warn('Date is not available in the options:', jsDateObject);
|
|
66
|
+
}
|
|
53
67
|
} else {
|
|
54
68
|
console.warn('Update with invalid date:', jsDateObject);
|
|
55
69
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { InputGroup } from './InputGroup.js';
|
|
2
|
+
import { jest } from '@jest/globals'
|
|
3
|
+
|
|
4
|
+
describe('InputGroup', () => {
|
|
5
|
+
let inputGroup;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
9
|
+
inputGroup = new InputGroup(new Date('2026-01-01'), new Date('2026-12-31'));
|
|
10
|
+
inputGroup.update(new Date('2026-03-15'));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.restoreAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('sets date March 15th 2026', () => {
|
|
18
|
+
expect(inputGroup.getValue()).toEqual('2026-03-15');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('sets date before the available options', () => {
|
|
22
|
+
inputGroup.update(new Date('2025-11-03'));
|
|
23
|
+
expect(inputGroup.getValue()).toEqual('2026-03-15');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('sets date after the available options', () => {
|
|
27
|
+
inputGroup.update(new Date('2027-02-17'));
|
|
28
|
+
expect(inputGroup.getValue()).toEqual('2026-03-15');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -39,4 +39,15 @@ export class MonthSelect {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
setMaxDate(jsDateObject) {
|
|
44
|
+
if(!jsDateObject) {
|
|
45
|
+
this.enableOptions();
|
|
46
|
+
} else {
|
|
47
|
+
const dayJsDate = dayjs(jsDateObject);
|
|
48
|
+
this.getOptions().forEach((option) => {
|
|
49
|
+
option.disabled = dayJsDate.isBefore(dayjs(option.value), 'month');
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
42
53
|
}
|
|
@@ -14,10 +14,10 @@ export class InputRange {
|
|
|
14
14
|
|
|
15
15
|
inputGroupFrom.getDaySelect().addEventListener('change', (event) => {
|
|
16
16
|
const newDate = new Date(event.target.value);
|
|
17
|
-
inputGroupTo.setMinDate(newDate);
|
|
18
17
|
|
|
19
18
|
if(dayjs(newDate).isAfter(dayjs(inputGroupTo.getValue()), 'day')) {
|
|
20
19
|
inputGroupTo.update(newDate);
|
|
20
|
+
inputGroupTo.setMinDate(newDate);
|
|
21
21
|
inputGroupTo.getDaySelect().dispatchEvent(new Event('change'));
|
|
22
22
|
}
|
|
23
23
|
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { InputGroup } from '../InputGroup/InputGroup.js';
|
|
3
|
+
import { InputRange } from '../InputRange/InputRange.js';
|
|
4
|
+
|
|
5
|
+
export class SelectDatepicker {
|
|
6
|
+
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this._init();
|
|
10
|
+
this.registerEventListeners();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_init() {
|
|
14
|
+
this.config.mode === 'range' ? this._initRange() : this._initSingle();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_initRange() {
|
|
18
|
+
const { start, end} = this._readAvailableOptions();
|
|
19
|
+
|
|
20
|
+
this.inputGroupFrom = new InputGroup(start, end);
|
|
21
|
+
this.inputGroupTo = new InputGroup(start, end);
|
|
22
|
+
|
|
23
|
+
new InputRange(this.inputGroupFrom, this.inputGroupTo);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_initSingle() {
|
|
27
|
+
this.inputGroupFrom = new InputGroup(this.config.availableOptions.start, this.config.availableOptions.end);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_readAvailableOptions() {
|
|
31
|
+
const defaultStart = dayjs().subtract(1, 'y').toDate();
|
|
32
|
+
const defaultEnd = dayjs().add(1, 'y').toDate();
|
|
33
|
+
|
|
34
|
+
const start = this.config.availableOptions?.start ?? defaultStart;
|
|
35
|
+
const end = this.config.availableOptions?.end ?? defaultEnd;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
start: start,
|
|
39
|
+
end: end
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setMinDate(date) {
|
|
44
|
+
if(this.inputGroupFrom) {
|
|
45
|
+
this.inputGroupFrom.setMinDate(date);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if(this.inputGroupTo) {
|
|
49
|
+
this.inputGroupTo.setMinDate(date);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setDate(date) {
|
|
54
|
+
if(date instanceof Array) {
|
|
55
|
+
if(date[0]) {
|
|
56
|
+
this.inputGroupFrom.update(this._convertDate(date[0]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if(date[1]) {
|
|
60
|
+
this.inputGroupTo.update(this._convertDate(date[1]));
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
this.inputGroupFrom.update(this._convertDate(date));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getDate() {
|
|
68
|
+
const value = [];
|
|
69
|
+
|
|
70
|
+
if(this.inputGroupFrom) {
|
|
71
|
+
value.push(this.inputGroupFrom.getValue());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if(this.inputGroupTo) {
|
|
75
|
+
value.push(this.inputGroupTo.getValue());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_convertDate(date) {
|
|
82
|
+
return dayjs(date).toDate();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getMarkup() {
|
|
86
|
+
const markup = document.createElement('div');
|
|
87
|
+
markup.className = 'imx-select-datepicker';
|
|
88
|
+
|
|
89
|
+
if(this.inputGroupFrom) {
|
|
90
|
+
markup.appendChild(this.inputGroupFrom.getMarkup());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if(this.inputGroupTo) {
|
|
94
|
+
markup.appendChild(this.inputGroupTo.getMarkup());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return markup;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getSelects() {
|
|
101
|
+
const selects = [];
|
|
102
|
+
|
|
103
|
+
if(this.inputGroupFrom) {
|
|
104
|
+
selects.push(this.inputGroupFrom.getDaySelect());
|
|
105
|
+
selects.push(this.inputGroupFrom.getMonthSelect());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if(this.inputGroupTo) {
|
|
109
|
+
selects.push(this.inputGroupTo.getDaySelect());
|
|
110
|
+
selects.push(this.inputGroupTo.getMonthSelect());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return selects;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
registerEventListeners() {
|
|
117
|
+
const listenerTargets = this.getSelects();
|
|
118
|
+
|
|
119
|
+
listenerTargets.forEach(target => {
|
|
120
|
+
target.addEventListener('change', () => {
|
|
121
|
+
const selectedDates = this.getDate();
|
|
122
|
+
if(this.config.onChange && typeof this.config.onChange === 'function') {
|
|
123
|
+
this.config.onChange(selectedDates);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { SelectDatepicker } from './SelectDatepicker.js';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { jest } from '@jest/globals'
|
|
4
|
+
|
|
5
|
+
describe('SelectDatepicker', () => {
|
|
6
|
+
let datepicker;
|
|
7
|
+
|
|
8
|
+
const defaultDateString = '2026-01-01';
|
|
9
|
+
|
|
10
|
+
const config = {
|
|
11
|
+
mode: 'range',
|
|
12
|
+
availableOptions: {
|
|
13
|
+
start: new Date('2026-01-01'),
|
|
14
|
+
end: new Date('2026-12-31')
|
|
15
|
+
},
|
|
16
|
+
onChange: jest.fn()
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
22
|
+
datepicker = new SelectDatepicker(config);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
jest.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('initialization', () => {
|
|
30
|
+
test('initializes in range mode', () => {
|
|
31
|
+
expect(datepicker.inputGroupFrom).toBeDefined();
|
|
32
|
+
expect(datepicker.inputGroupTo).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('initializes in single mode', () => {
|
|
36
|
+
const singleConfig = {
|
|
37
|
+
mode: 'single',
|
|
38
|
+
availableOptions: {
|
|
39
|
+
start: new Date('2026-01-01'),
|
|
40
|
+
end: new Date('2026-12-31')
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const singleDatepicker = new SelectDatepicker(singleConfig);
|
|
44
|
+
expect(singleDatepicker.inputGroupFrom).toBeDefined();
|
|
45
|
+
expect(singleDatepicker.inputGroupTo).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('setDate', () => {
|
|
50
|
+
test('sets single date in range mode', () => {
|
|
51
|
+
const dateString = '2026-03-15';
|
|
52
|
+
const date = new Date(dateString);
|
|
53
|
+
datepicker.setDate(date);
|
|
54
|
+
expect(datepicker.getDate()).toEqual([dateString, defaultDateString]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('sets date range with array', () => {
|
|
58
|
+
const fromDateString = '2026-03-15';
|
|
59
|
+
const toDateString = '2026-06-20';
|
|
60
|
+
|
|
61
|
+
const fromDate = new Date(fromDateString);
|
|
62
|
+
const toDate = new Date(toDateString);
|
|
63
|
+
|
|
64
|
+
datepicker.setDate([fromDate, toDate]);
|
|
65
|
+
expect(datepicker.getDate()).toEqual([fromDateString, toDateString]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('handles partial array (only start date)', () => {
|
|
69
|
+
const fromDateString = '2026-03-15';
|
|
70
|
+
const fromDate = new Date(fromDateString);
|
|
71
|
+
|
|
72
|
+
datepicker.setDate([fromDate]);
|
|
73
|
+
expect(datepicker.getDate()).toEqual([fromDateString, defaultDateString]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('sets an invalid date', () => {
|
|
77
|
+
const fromDateString = 'invalid';
|
|
78
|
+
const fromDate = new Date(fromDateString);
|
|
79
|
+
|
|
80
|
+
datepicker.setDate([fromDate]);
|
|
81
|
+
expect(datepicker.getDate()).toEqual([defaultDateString, defaultDateString]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('getDate', () => {
|
|
86
|
+
test('returns array of selected dates', () => {
|
|
87
|
+
datepicker.setDate([new Date('2026-03-15'), new Date('2026-06-20')]);
|
|
88
|
+
const dates = datepicker.getDate();
|
|
89
|
+
|
|
90
|
+
expect(Array.isArray(dates)).toBe(true);
|
|
91
|
+
expect(dates.length).toBe(2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('returns single date in range mode', () => {
|
|
95
|
+
datepicker.setDate(new Date('2026-03-15'));
|
|
96
|
+
const dates = datepicker.getDate();
|
|
97
|
+
|
|
98
|
+
expect(dates.length).toBeGreaterThan(0);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('returns the default dates if no availableOptions are passed', () => {
|
|
102
|
+
const emptyDatepicker = new SelectDatepicker({
|
|
103
|
+
mode: 'range',
|
|
104
|
+
});
|
|
105
|
+
const dates = emptyDatepicker.getDate();
|
|
106
|
+
expect(dates).toEqual([dayjs().subtract(1, 'year').startOf('month').format('YYYY-MM-DD'), dayjs().subtract(1, 'year').startOf('month').format('YYYY-MM-DD')]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('getMarkup', () => {
|
|
111
|
+
test('returns a div element', () => {
|
|
112
|
+
const markup = datepicker.getMarkup();
|
|
113
|
+
expect(markup.tagName).toBe('DIV');
|
|
114
|
+
expect(markup.className).toBe('imx-select-datepicker');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('includes both input groups in markup', () => {
|
|
118
|
+
const markup = datepicker.getMarkup();
|
|
119
|
+
expect(markup.children.length).toBe(2);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('getSelects', () => {
|
|
124
|
+
test('returns array of select elements', () => {
|
|
125
|
+
const selects = datepicker.getSelects();
|
|
126
|
+
expect(Array.isArray(selects)).toBe(true);
|
|
127
|
+
expect(selects.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('returns 4 selects in range mode (day, month x2)', () => {
|
|
131
|
+
const selects = datepicker.getSelects();
|
|
132
|
+
expect(selects.length).toBe(4);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('registerEventListeners', () => {
|
|
137
|
+
test('calls onChange callback on select change', () => {
|
|
138
|
+
const selects = datepicker.getSelects();
|
|
139
|
+
const event = new Event('change');
|
|
140
|
+
|
|
141
|
+
selects[0].dispatchEvent(event);
|
|
142
|
+
expect(config.onChange).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('does not call onChange if not provided', () => {
|
|
146
|
+
const noCbConfig = {
|
|
147
|
+
mode: 'range',
|
|
148
|
+
availableOptions: {
|
|
149
|
+
start: new Date('2026-01-01'),
|
|
150
|
+
end: new Date('2026-12-31')
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const noCbDatepicker = new SelectDatepicker(noCbConfig);
|
|
154
|
+
const selects = noCbDatepicker.getSelects();
|
|
155
|
+
const event = new Event('change');
|
|
156
|
+
|
|
157
|
+
expect(() => selects[0].dispatchEvent(event)).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|