@ministryofjustice/frontend 3.4.0 → 3.6.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/moj/all.jquery.js +13378 -0
- package/moj/all.jquery.min.js +1 -144
- package/moj/all.js +2266 -2551
- package/moj/all.mjs +126 -0
- package/moj/components/add-another/add-another.js +110 -100
- package/moj/components/add-another/add-another.mjs +106 -0
- package/moj/components/alert/alert.js +319 -211
- package/moj/components/alert/alert.mjs +251 -0
- package/moj/components/alert/alert.spec.helper.js +12 -5
- package/moj/components/alert/alert.spec.helper.mjs +66 -0
- package/moj/components/button-menu/button-menu.js +302 -292
- package/moj/components/button-menu/button-menu.mjs +329 -0
- package/moj/components/date-picker/date-picker.js +850 -842
- package/moj/components/date-picker/date-picker.mjs +961 -0
- package/moj/components/filter-toggle-button/filter-toggle-button.js +98 -88
- package/moj/components/filter-toggle-button/filter-toggle-button.mjs +93 -0
- package/moj/components/form-validator/form-validator.js +195 -155
- package/moj/components/form-validator/form-validator.mjs +168 -0
- package/moj/components/multi-file-upload/multi-file-upload.js +158 -137
- package/moj/components/multi-file-upload/multi-file-upload.mjs +219 -0
- package/moj/components/multi-select/multi-select.js +75 -65
- package/moj/components/multi-select/multi-select.mjs +77 -0
- package/moj/components/password-reveal/password-reveal.js +40 -30
- package/moj/components/password-reveal/password-reveal.mjs +35 -0
- package/moj/components/rich-text-editor/rich-text-editor.js +92 -80
- package/moj/components/rich-text-editor/rich-text-editor.mjs +157 -0
- package/moj/components/search-toggle/search-toggle.js +55 -45
- package/moj/components/search-toggle/search-toggle.mjs +54 -0
- package/moj/components/sortable-table/sortable-table.js +141 -141
- package/moj/components/sortable-table/sortable-table.mjs +138 -0
- package/moj/helpers/_links.scss +1 -1
- package/moj/helpers.js +171 -152
- package/moj/helpers.mjs +123 -0
- package/moj/moj-frontend.min.js +1 -144
- package/moj/version.js +11 -1
- package/moj/version.mjs +3 -0
- package/package.json +13 -1
- package/moj/all.spec.js +0 -24
- package/moj/components/add-another/add-another.spec.js +0 -165
- package/moj/components/alert/alert.spec.js +0 -229
- package/moj/components/button-menu/button-menu.spec.js +0 -360
- package/moj/components/date-picker/date-picker.spec.js +0 -1178
- package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +0 -302
- package/moj/components/multi-file-upload/multi-file-upload.spec.js +0 -510
- package/moj/components/multi-select/multi-select.spec.js +0 -128
- package/moj/components/password-reveal/password-reveal.spec.js +0 -57
- package/moj/components/search-toggle/search-toggle.spec.js +0 -129
- package/moj/components/sortable-table/sortable-table.spec.js +0 -362
- package/moj/helpers.spec.js +0 -235
- package/moj/namespace.js +0 -2
|
@@ -1,236 +1,242 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
leadingZeros: { type: 'string' },
|
|
28
|
-
maxDate: { type: 'string' },
|
|
29
|
-
minDate: { type: 'string' },
|
|
30
|
-
weekStartDay: { type: 'string' }
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MOJFrontend = global.MOJFrontend || {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Date picker config
|
|
9
|
+
*
|
|
10
|
+
* @typedef {object} DatePickerConfig
|
|
11
|
+
* @property {string} [excludedDates] - Dates that cannot be selected
|
|
12
|
+
* @property {string} [excludedDays] - Days that cannot be selected
|
|
13
|
+
* @property {boolean} [leadingZeroes] - Whether to add leading zeroes when populating the field
|
|
14
|
+
* @property {string} [minDate] - The earliest available date
|
|
15
|
+
* @property {string} [maxDate] - The latest available date
|
|
16
|
+
* @property {string} [weekStartDay] - First day of the week in calendar view
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {HTMLElement} $module - HTML element
|
|
21
|
+
* @param {DatePickerConfig} config - config object
|
|
22
|
+
* @class
|
|
23
|
+
*/
|
|
24
|
+
function DatePicker($module, config = {}) {
|
|
25
|
+
if (!$module) {
|
|
26
|
+
return this
|
|
31
27
|
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const defaults = {
|
|
35
|
-
leadingZeros: false,
|
|
36
|
-
weekStartDay: 'monday'
|
|
37
|
-
}
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
29
|
+
const schema = Object.freeze({
|
|
30
|
+
properties: {
|
|
31
|
+
excludedDates: { type: 'string' },
|
|
32
|
+
excludedDays: { type: 'string' },
|
|
33
|
+
leadingZeros: { type: 'string' },
|
|
34
|
+
maxDate: { type: 'string' },
|
|
35
|
+
minDate: { type: 'string' },
|
|
36
|
+
weekStartDay: { type: 'string' }
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const defaults = {
|
|
41
|
+
leadingZeros: false,
|
|
42
|
+
weekStartDay: 'monday'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// data attributes override JS config, which overrides defaults
|
|
46
|
+
this.config = this.mergeConfigs(
|
|
47
|
+
defaults,
|
|
48
|
+
config,
|
|
49
|
+
this.parseDataset(schema, $module.dataset)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
this.dayLabels = [
|
|
53
|
+
'Monday',
|
|
54
|
+
'Tuesday',
|
|
55
|
+
'Wednesday',
|
|
56
|
+
'Thursday',
|
|
57
|
+
'Friday',
|
|
58
|
+
'Saturday',
|
|
59
|
+
'Sunday'
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
this.monthLabels = [
|
|
63
|
+
'January',
|
|
64
|
+
'February',
|
|
65
|
+
'March',
|
|
66
|
+
'April',
|
|
67
|
+
'May',
|
|
68
|
+
'June',
|
|
69
|
+
'July',
|
|
70
|
+
'August',
|
|
71
|
+
'September',
|
|
72
|
+
'October',
|
|
73
|
+
'November',
|
|
74
|
+
'December'
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
this.currentDate = new Date();
|
|
78
|
+
this.currentDate.setHours(0, 0, 0, 0);
|
|
79
|
+
this.calendarDays = [];
|
|
80
|
+
this.excludedDates = [];
|
|
81
|
+
this.excludedDays = [];
|
|
82
|
+
|
|
83
|
+
this.buttonClass = 'moj-datepicker__button';
|
|
84
|
+
this.selectedDayButtonClass = 'moj-datepicker__button--selected';
|
|
85
|
+
this.currentDayButtonClass = 'moj-datepicker__button--current';
|
|
86
|
+
this.todayButtonClass = 'moj-datepicker__button--today';
|
|
87
|
+
|
|
88
|
+
this.$module = $module;
|
|
89
|
+
this.$input = $module.querySelector('.moj-js-datepicker-input');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
DatePicker.prototype.init = function () {
|
|
93
|
+
// Check that required elements are present
|
|
94
|
+
if (!this.$input) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
if (this.$module.dataset.initialized) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
101
|
+
this.setOptions();
|
|
102
|
+
this.initControls();
|
|
103
|
+
this.$module.setAttribute('data-initialized', 'true');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
DatePicker.prototype.initControls = function () {
|
|
107
|
+
this.id = `datepicker-${this.$input.id}`;
|
|
108
|
+
|
|
109
|
+
this.$dialog = this.createDialog();
|
|
110
|
+
this.createCalendarHeaders();
|
|
111
|
+
|
|
112
|
+
const $componentWrapper = document.createElement('div');
|
|
113
|
+
const $inputWrapper = document.createElement('div');
|
|
114
|
+
$componentWrapper.classList.add('moj-datepicker__wrapper');
|
|
115
|
+
$inputWrapper.classList.add('govuk-input__wrapper');
|
|
116
|
+
|
|
117
|
+
this.$input.parentNode.insertBefore($componentWrapper, this.$input);
|
|
118
|
+
$componentWrapper.appendChild($inputWrapper);
|
|
119
|
+
$inputWrapper.appendChild(this.$input);
|
|
120
|
+
|
|
121
|
+
$inputWrapper.insertAdjacentHTML('beforeend', this.toggleTemplate());
|
|
122
|
+
$componentWrapper.insertAdjacentElement('beforeend', this.$dialog);
|
|
123
|
+
|
|
124
|
+
this.$calendarButton = this.$module.querySelector('.moj-js-datepicker-toggle');
|
|
125
|
+
this.$dialogTitle = this.$dialog.querySelector(
|
|
126
|
+
'.moj-js-datepicker-month-year'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
this.createCalendar();
|
|
130
|
+
|
|
131
|
+
this.$prevMonthButton = this.$dialog.querySelector(
|
|
132
|
+
'.moj-js-datepicker-prev-month'
|
|
133
|
+
);
|
|
134
|
+
this.$prevYearButton = this.$dialog.querySelector(
|
|
135
|
+
'.moj-js-datepicker-prev-year'
|
|
136
|
+
);
|
|
137
|
+
this.$nextMonthButton = this.$dialog.querySelector(
|
|
138
|
+
'.moj-js-datepicker-next-month'
|
|
139
|
+
);
|
|
140
|
+
this.$nextYearButton = this.$dialog.querySelector(
|
|
141
|
+
'.moj-js-datepicker-next-year'
|
|
142
|
+
);
|
|
143
|
+
this.$cancelButton = this.$dialog.querySelector('.moj-js-datepicker-cancel');
|
|
144
|
+
this.$okButton = this.$dialog.querySelector('.moj-js-datepicker-ok');
|
|
145
|
+
|
|
146
|
+
// add event listeners
|
|
147
|
+
this.$prevMonthButton.addEventListener('click', (event) =>
|
|
148
|
+
this.focusPreviousMonth(event, false)
|
|
149
|
+
);
|
|
150
|
+
this.$prevYearButton.addEventListener('click', (event) =>
|
|
151
|
+
this.focusPreviousYear(event, false)
|
|
152
|
+
);
|
|
153
|
+
this.$nextMonthButton.addEventListener('click', (event) =>
|
|
154
|
+
this.focusNextMonth(event, false)
|
|
155
|
+
);
|
|
156
|
+
this.$nextYearButton.addEventListener('click', (event) =>
|
|
157
|
+
this.focusNextYear(event, false)
|
|
158
|
+
);
|
|
159
|
+
this.$cancelButton.addEventListener('click', (event) => {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
this.closeDialog(event);
|
|
162
|
+
});
|
|
163
|
+
this.$okButton.addEventListener('click', () => {
|
|
164
|
+
this.selectDate(this.currentDate);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const dialogButtons = this.$dialog.querySelectorAll(
|
|
168
|
+
'button:not([disabled="true"])'
|
|
169
|
+
);
|
|
170
|
+
// eslint-disable-next-line prefer-destructuring
|
|
171
|
+
this.$firstButtonInDialog = dialogButtons[0];
|
|
172
|
+
this.$lastButtonInDialog = dialogButtons[dialogButtons.length - 1];
|
|
173
|
+
this.$firstButtonInDialog.addEventListener('keydown', (event) =>
|
|
174
|
+
this.firstButtonKeydown(event)
|
|
175
|
+
);
|
|
176
|
+
this.$lastButtonInDialog.addEventListener('keydown', (event) =>
|
|
177
|
+
this.lastButtonKeydown(event)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
this.$calendarButton.addEventListener('click', (event) =>
|
|
181
|
+
this.toggleDialog(event)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
this.$dialog.addEventListener('keydown', (event) => {
|
|
185
|
+
if (event.key === 'Escape') {
|
|
186
|
+
this.closeDialog();
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
event.stopPropagation();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
document.body.addEventListener('mouseup', (event) =>
|
|
193
|
+
this.backgroundClick(event)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// populates calendar with initial dates, avoids Wave errors about null buttons
|
|
197
|
+
this.updateCalendar();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
DatePicker.prototype.createDialog = function () {
|
|
201
|
+
const titleId = `datepicker-title-${this.$input.id}`;
|
|
202
|
+
const $dialog = document.createElement('div');
|
|
203
|
+
|
|
204
|
+
$dialog.id = this.id;
|
|
205
|
+
$dialog.setAttribute('class', 'moj-datepicker__dialog');
|
|
206
|
+
$dialog.setAttribute('role', 'dialog');
|
|
207
|
+
$dialog.setAttribute('aria-modal', 'true');
|
|
208
|
+
$dialog.setAttribute('aria-labelledby', titleId);
|
|
209
|
+
$dialog.innerHTML = this.dialogTemplate(titleId);
|
|
210
|
+
$dialog.hidden = true;
|
|
211
|
+
|
|
212
|
+
return $dialog
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
DatePicker.prototype.createCalendar = function () {
|
|
216
|
+
const $tbody = this.$dialog.querySelector('tbody');
|
|
217
|
+
let dayCount = 0;
|
|
218
|
+
for (let i = 0; i < 6; i++) {
|
|
219
|
+
// create row
|
|
220
|
+
const $row = $tbody.insertRow(i);
|
|
221
|
+
|
|
222
|
+
for (let j = 0; j < 7; j++) {
|
|
223
|
+
// create cell (day)
|
|
224
|
+
const $cell = document.createElement('td');
|
|
225
|
+
const $dateButton = document.createElement('button');
|
|
226
|
+
|
|
227
|
+
$cell.appendChild($dateButton);
|
|
228
|
+
$row.appendChild($cell);
|
|
229
|
+
|
|
230
|
+
const calendarDay = new DSCalendarDay($dateButton, dayCount, i, j, this);
|
|
231
|
+
calendarDay.init();
|
|
232
|
+
this.calendarDays.push(calendarDay);
|
|
233
|
+
dayCount++;
|
|
234
|
+
}
|
|
228
235
|
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
236
|
+
};
|
|
231
237
|
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
DatePicker.prototype.toggleTemplate = function () {
|
|
239
|
+
return `<button class="moj-datepicker__toggle moj-js-datepicker-toggle" type="button" aria-haspopup="dialog" aria-controls="${this.id}" aria-expanded="false">
|
|
234
240
|
<span class="govuk-visually-hidden">Choose date</span>
|
|
235
241
|
<svg width="32" height="24" focusable="false" class="moj-datepicker-icon" aria-hidden="true" role="img" viewBox="0 0 22 22">
|
|
236
242
|
<path
|
|
@@ -243,16 +249,16 @@ Datepicker.prototype.toggleTemplate = function () {
|
|
|
243
249
|
<rect x="16.8667" width="1.46667" height="5.13333" rx="0.733333" fill="currentColor"></rect>
|
|
244
250
|
</svg>
|
|
245
251
|
</button>`
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* HTML template for calendar dialog
|
|
256
|
+
*
|
|
257
|
+
* @param {string} [titleId] - Id attribute for dialog title
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
DatePicker.prototype.dialogTemplate = function (titleId) {
|
|
261
|
+
return `<div class="moj-datepicker__dialog-header">
|
|
256
262
|
<div class="moj-datepicker__dialog-navbuttons">
|
|
257
263
|
<button class="moj-datepicker__button moj-js-datepicker-prev-year">
|
|
258
264
|
<span class="govuk-visually-hidden">Previous year</span>
|
|
@@ -302,660 +308,662 @@ Datepicker.prototype.dialogTemplate = function (titleId) {
|
|
|
302
308
|
<button type="button" class="govuk-button moj-js-datepicker-ok">Select</button>
|
|
303
309
|
<button type="button" class="govuk-button govuk-button--secondary moj-js-datepicker-cancel">Close</button>
|
|
304
310
|
</div>`
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return ret
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
Datepicker.prototype.setOptions = function () {
|
|
333
|
-
this.setMinAndMaxDatesOnCalendar()
|
|
334
|
-
this.setExcludedDates()
|
|
335
|
-
this.setExcludedDays()
|
|
336
|
-
this.setLeadingZeros()
|
|
337
|
-
this.setWeekStartDay()
|
|
338
|
-
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
DatePicker.prototype.createCalendarHeaders = function () {
|
|
314
|
+
this.dayLabels.forEach((day) => {
|
|
315
|
+
const html = `<th scope="col"><span aria-hidden="true">${day.substring(0, 3)}</span><span class="govuk-visually-hidden">${day}</span></th>`;
|
|
316
|
+
const $headerRow = this.$dialog.querySelector('thead > tr');
|
|
317
|
+
$headerRow.insertAdjacentHTML('beforeend', html);
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Pads given number with leading zeros
|
|
323
|
+
*
|
|
324
|
+
* @param {number} value - The value to be padded
|
|
325
|
+
* @param {number} length - The length in characters of the output
|
|
326
|
+
* @returns {string}
|
|
327
|
+
*/
|
|
328
|
+
DatePicker.prototype.leadingZeros = function (value, length = 2) {
|
|
329
|
+
let ret = value.toString();
|
|
330
|
+
|
|
331
|
+
while (ret.length < length) {
|
|
332
|
+
ret = `0${ret}`;
|
|
333
|
+
}
|
|
339
334
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
335
|
+
return ret
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
DatePicker.prototype.setOptions = function () {
|
|
339
|
+
this.setMinAndMaxDatesOnCalendar();
|
|
340
|
+
this.setExcludedDates();
|
|
341
|
+
this.setExcludedDays();
|
|
342
|
+
this.setLeadingZeros();
|
|
343
|
+
this.setWeekStartDay();
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
DatePicker.prototype.setMinAndMaxDatesOnCalendar = function () {
|
|
347
|
+
if (this.config.minDate) {
|
|
348
|
+
this.minDate = this.formattedDateFromString(this.config.minDate, null);
|
|
349
|
+
if (this.minDate && this.currentDate < this.minDate) {
|
|
350
|
+
this.currentDate = this.minDate;
|
|
351
|
+
}
|
|
345
352
|
}
|
|
346
|
-
}
|
|
347
353
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
354
|
+
if (this.config.maxDate) {
|
|
355
|
+
this.maxDate = this.formattedDateFromString(this.config.maxDate, null);
|
|
356
|
+
if (this.maxDate && this.currentDate > this.maxDate) {
|
|
357
|
+
this.currentDate = this.maxDate;
|
|
358
|
+
}
|
|
352
359
|
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
.filter((item) => item)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/*
|
|
372
|
-
* Parses a daterange string into an array of dates
|
|
373
|
-
* @param {String} datestring - A daterange string in the format "dd/mm/yyyy-dd/mm/yyyy"
|
|
374
|
-
* @returns {Date[]}
|
|
375
|
-
*/
|
|
376
|
-
Datepicker.prototype.parseDateRangeString = function (datestring) {
|
|
377
|
-
const dates = []
|
|
378
|
-
const [startDate, endDate] = datestring
|
|
379
|
-
.split('-')
|
|
380
|
-
.map((d) => this.formattedDateFromString(d, null))
|
|
381
|
-
|
|
382
|
-
if (startDate && endDate) {
|
|
383
|
-
const date = new Date(startDate.getTime())
|
|
384
|
-
/* eslint-disable no-unmodified-loop-condition */
|
|
385
|
-
while (date <= endDate) {
|
|
386
|
-
dates.push(new Date(date))
|
|
387
|
-
date.setDate(date.getDate() + 1)
|
|
388
|
-
}
|
|
389
|
-
/* eslint-enable no-unmodified-loop-condition */
|
|
390
|
-
}
|
|
391
|
-
return dates
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
Datepicker.prototype.setExcludedDays = function () {
|
|
395
|
-
if (this.config.excludedDays) {
|
|
396
|
-
// lowercase and arrange dayLabels to put indexOf sunday == 0 for comparison
|
|
397
|
-
// with getDay() function
|
|
398
|
-
const weekDays = this.dayLabels.map((item) => item.toLowerCase())
|
|
399
|
-
if (this.config.weekStartDay === 'monday') {
|
|
400
|
-
weekDays.unshift(weekDays.pop())
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
DatePicker.prototype.setExcludedDates = function () {
|
|
363
|
+
if (this.config.excludedDates) {
|
|
364
|
+
this.excludedDates = this.config.excludedDates
|
|
365
|
+
.replace(/\s+/, ' ')
|
|
366
|
+
.split(' ')
|
|
367
|
+
.map((item) => {
|
|
368
|
+
return item.includes('-')
|
|
369
|
+
? this.parseDateRangeString(item)
|
|
370
|
+
: this.formattedDateFromString(item)
|
|
371
|
+
})
|
|
372
|
+
.flat()
|
|
373
|
+
.filter((item) => item);
|
|
401
374
|
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/*
|
|
378
|
+
* Parses a daterange string into an array of dates
|
|
379
|
+
* @param {String} datestring - A daterange string in the format "dd/mm/yyyy-dd/mm/yyyy"
|
|
380
|
+
* @returns {Date[]}
|
|
381
|
+
*/
|
|
382
|
+
DatePicker.prototype.parseDateRangeString = function (datestring) {
|
|
383
|
+
const dates = [];
|
|
384
|
+
const [startDate, endDate] = datestring
|
|
385
|
+
.split('-')
|
|
386
|
+
.map((d) => this.formattedDateFromString(d, null));
|
|
387
|
+
|
|
388
|
+
if (startDate && endDate) {
|
|
389
|
+
const date = new Date(startDate.getTime());
|
|
390
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
391
|
+
while (date <= endDate) {
|
|
392
|
+
dates.push(new Date(date));
|
|
393
|
+
date.setDate(date.getDate() + 1);
|
|
394
|
+
}
|
|
395
|
+
/* eslint-enable no-unmodified-loop-condition */
|
|
396
|
+
}
|
|
397
|
+
return dates
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
DatePicker.prototype.setExcludedDays = function () {
|
|
401
|
+
if (this.config.excludedDays) {
|
|
402
|
+
// lowercase and arrange dayLabels to put indexOf sunday == 0 for comparison
|
|
403
|
+
// with getDay() function
|
|
404
|
+
const weekDays = this.dayLabels.map((item) => item.toLowerCase());
|
|
405
|
+
if (this.config.weekStartDay === 'monday') {
|
|
406
|
+
weekDays.unshift(weekDays.pop());
|
|
407
|
+
}
|
|
402
408
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
409
|
+
this.excludedDays = this.config.excludedDays
|
|
410
|
+
.replace(/\s+/, ' ')
|
|
411
|
+
.toLowerCase()
|
|
412
|
+
.split(' ')
|
|
413
|
+
.map((item) => weekDays.indexOf(item))
|
|
414
|
+
.filter((item) => item !== -1);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
411
417
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
418
|
+
DatePicker.prototype.setLeadingZeros = function () {
|
|
419
|
+
if (typeof this.config.leadingZeros !== 'boolean') {
|
|
420
|
+
if (this.config.leadingZeros.toLowerCase() === 'true') {
|
|
421
|
+
this.config.leadingZeros = true;
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
if (this.config.leadingZeros.toLowerCase() === 'false') {
|
|
425
|
+
this.config.leadingZeros = false;
|
|
426
|
+
}
|
|
417
427
|
}
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
DatePicker.prototype.setWeekStartDay = function () {
|
|
431
|
+
const weekStartDayParam = this.config.weekStartDay;
|
|
432
|
+
if (weekStartDayParam && weekStartDayParam.toLowerCase() === 'sunday') {
|
|
433
|
+
this.config.weekStartDay = 'sunday';
|
|
434
|
+
// Rotate dayLabels array to put Sunday as the first item
|
|
435
|
+
this.dayLabels.unshift(this.dayLabels.pop());
|
|
436
|
+
} else {
|
|
437
|
+
this.config.weekStartDay = 'monday';
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Determine if a date is selecteable
|
|
443
|
+
*
|
|
444
|
+
* @param {Date} date - the date to check
|
|
445
|
+
* @returns {boolean}
|
|
446
|
+
*/
|
|
447
|
+
DatePicker.prototype.isExcludedDate = function (date) {
|
|
448
|
+
// This comparison does not work correctly - it will exclude the mindate itself
|
|
449
|
+
// see: https://github.com/ministryofjustice/moj-frontend/issues/923
|
|
450
|
+
if (this.minDate && this.minDate > date) {
|
|
451
|
+
return true
|
|
420
452
|
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
Datepicker.prototype.setWeekStartDay = function () {
|
|
425
|
-
const weekStartDayParam = this.config.weekStartDay
|
|
426
|
-
if (weekStartDayParam && weekStartDayParam.toLowerCase() === 'sunday') {
|
|
427
|
-
this.config.weekStartDay = 'sunday'
|
|
428
|
-
// Rotate dayLabels array to put Sunday as the first item
|
|
429
|
-
this.dayLabels.unshift(this.dayLabels.pop())
|
|
430
|
-
} else {
|
|
431
|
-
this.config.weekStartDay = 'monday'
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Determine if a date is selecteable
|
|
437
|
-
*
|
|
438
|
-
* @param {Date} date - the date to check
|
|
439
|
-
* @returns {boolean}
|
|
440
|
-
*/
|
|
441
|
-
Datepicker.prototype.isExcludedDate = function (date) {
|
|
442
|
-
// This comparison does not work correctly - it will exclude the mindate itself
|
|
443
|
-
// see: https://github.com/ministryofjustice/moj-frontend/issues/923
|
|
444
|
-
if (this.minDate && this.minDate > date) {
|
|
445
|
-
return true
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// This comparison works as expected - the maxdate will not be excluded
|
|
449
|
-
if (this.maxDate && this.maxDate < date) {
|
|
450
|
-
return true
|
|
451
|
-
}
|
|
452
453
|
|
|
453
|
-
|
|
454
|
-
if (
|
|
454
|
+
// This comparison works as expected - the maxdate will not be excluded
|
|
455
|
+
if (this.maxDate && this.maxDate < date) {
|
|
455
456
|
return true
|
|
456
457
|
}
|
|
457
|
-
}
|
|
458
458
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
for (const excludedDate of this.excludedDates) {
|
|
460
|
+
if (date.toDateString() === excludedDate.toDateString()) {
|
|
461
|
+
return true
|
|
462
|
+
}
|
|
463
|
+
}
|
|
462
464
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Get a Date object from a string
|
|
468
|
-
*
|
|
469
|
-
* @param {string} dateString - string in the format d/m/yyyy dd/mm/yyyy
|
|
470
|
-
* @param {Date} fallback - date object to return if formatting fails
|
|
471
|
-
* @returns {Date}
|
|
472
|
-
*/
|
|
473
|
-
Datepicker.prototype.formattedDateFromString = function (
|
|
474
|
-
dateString,
|
|
475
|
-
fallback = new Date()
|
|
476
|
-
) {
|
|
477
|
-
let formattedDate = null
|
|
478
|
-
// Accepts d/m/yyyy and dd/mm/yyyy
|
|
479
|
-
const dateFormatPattern = /(\d{1,2})([-/,. ])(\d{1,2})\2(\d{4})/
|
|
480
|
-
|
|
481
|
-
if (!dateFormatPattern.test(dateString)) return fallback
|
|
482
|
-
|
|
483
|
-
const match = dateString.match(dateFormatPattern)
|
|
484
|
-
const day = match[1]
|
|
485
|
-
const month = match[3]
|
|
486
|
-
const year = match[4]
|
|
487
|
-
|
|
488
|
-
formattedDate = new Date(`${year}-${month}-${day}`)
|
|
489
|
-
if (formattedDate instanceof Date && !isNaN(formattedDate)) {
|
|
490
|
-
return formattedDate
|
|
491
|
-
}
|
|
492
|
-
return fallback
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Get a formatted date string from a Date object
|
|
497
|
-
*
|
|
498
|
-
* @param {Date} date - date to format to a string
|
|
499
|
-
* @returns {string}
|
|
500
|
-
*/
|
|
501
|
-
Datepicker.prototype.formattedDateFromDate = function (date) {
|
|
502
|
-
if (this.config.leadingZeros) {
|
|
503
|
-
return `${this.leadingZeros(date.getDate())}/${this.leadingZeros(date.getMonth() + 1)}/${date.getFullYear()}`
|
|
504
|
-
}
|
|
465
|
+
if (this.excludedDays.includes(date.getDay())) {
|
|
466
|
+
return true
|
|
467
|
+
}
|
|
505
468
|
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
Datepicker.prototype.backgroundClick = function (event) {
|
|
520
|
-
if (
|
|
521
|
-
this.isOpen() &&
|
|
522
|
-
!this.$dialog.contains(event.target) &&
|
|
523
|
-
!this.$input.contains(event.target) &&
|
|
524
|
-
!this.$calendarButton.contains(event.target)
|
|
469
|
+
return false
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get a Date object from a string
|
|
474
|
+
*
|
|
475
|
+
* @param {string} dateString - string in the format d/m/yyyy dd/mm/yyyy
|
|
476
|
+
* @param {Date} fallback - date object to return if formatting fails
|
|
477
|
+
* @returns {Date}
|
|
478
|
+
*/
|
|
479
|
+
DatePicker.prototype.formattedDateFromString = function (
|
|
480
|
+
dateString,
|
|
481
|
+
fallback = new Date()
|
|
525
482
|
) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
483
|
+
let formattedDate = null;
|
|
484
|
+
// Accepts d/m/yyyy and dd/mm/yyyy
|
|
485
|
+
const dateFormatPattern = /(\d{1,2})([-/,. ])(\d{1,2})\2(\d{4})/;
|
|
530
486
|
|
|
531
|
-
|
|
532
|
-
if (event.key === 'Tab' && event.shiftKey) {
|
|
533
|
-
this.$lastButtonInDialog.focus()
|
|
534
|
-
event.preventDefault()
|
|
535
|
-
}
|
|
536
|
-
}
|
|
487
|
+
if (!dateFormatPattern.test(dateString)) return fallback
|
|
537
488
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
}
|
|
489
|
+
const match = dateString.match(dateFormatPattern);
|
|
490
|
+
const day = match[1];
|
|
491
|
+
const month = match[3];
|
|
492
|
+
const year = match[4];
|
|
544
493
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
494
|
+
formattedDate = new Date(`${year}-${month}-${day}`);
|
|
495
|
+
if (formattedDate instanceof Date && !isNaN(formattedDate)) {
|
|
496
|
+
return formattedDate
|
|
497
|
+
}
|
|
498
|
+
return fallback
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get a formatted date string from a Date object
|
|
503
|
+
*
|
|
504
|
+
* @param {Date} date - date to format to a string
|
|
505
|
+
* @returns {string}
|
|
506
|
+
*/
|
|
507
|
+
DatePicker.prototype.formattedDateFromDate = function (date) {
|
|
508
|
+
if (this.config.leadingZeros) {
|
|
509
|
+
return `${this.leadingZeros(date.getDate())}/${this.leadingZeros(date.getMonth() + 1)}/${date.getFullYear()}`
|
|
510
|
+
}
|
|
548
511
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
let dayOfWeek
|
|
512
|
+
return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`
|
|
513
|
+
};
|
|
552
514
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
515
|
+
/**
|
|
516
|
+
* Get a human readable date in the format Monday 2 March 2024
|
|
517
|
+
*
|
|
518
|
+
* @param {Date} date - date to format
|
|
519
|
+
* @returns {string}
|
|
520
|
+
*/
|
|
521
|
+
DatePicker.prototype.formattedDateHuman = function (date) {
|
|
522
|
+
return `${this.dayLabels[(date.getDay() + 6) % 7]} ${date.getDate()} ${this.monthLabels[date.getMonth()]} ${date.getFullYear()}`
|
|
523
|
+
};
|
|
558
524
|
|
|
559
|
-
|
|
525
|
+
DatePicker.prototype.backgroundClick = function (event) {
|
|
526
|
+
if (
|
|
527
|
+
this.isOpen() &&
|
|
528
|
+
!this.$dialog.contains(event.target) &&
|
|
529
|
+
!this.$input.contains(event.target) &&
|
|
530
|
+
!this.$calendarButton.contains(event.target)
|
|
531
|
+
) {
|
|
532
|
+
event.preventDefault();
|
|
533
|
+
this.closeDialog();
|
|
534
|
+
}
|
|
535
|
+
};
|
|
560
536
|
|
|
561
|
-
|
|
537
|
+
DatePicker.prototype.firstButtonKeydown = function (event) {
|
|
538
|
+
if (event.key === 'Tab' && event.shiftKey) {
|
|
539
|
+
this.$lastButtonInDialog.focus();
|
|
540
|
+
event.preventDefault();
|
|
541
|
+
}
|
|
542
|
+
};
|
|
562
543
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
544
|
+
DatePicker.prototype.lastButtonKeydown = function (event) {
|
|
545
|
+
if (event.key === 'Tab' && !event.shiftKey) {
|
|
546
|
+
this.$firstButtonInDialog.focus();
|
|
547
|
+
event.preventDefault();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
567
550
|
|
|
568
|
-
|
|
551
|
+
// render calendar
|
|
552
|
+
DatePicker.prototype.updateCalendar = function () {
|
|
553
|
+
this.$dialogTitle.innerHTML = `${this.monthLabels[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;
|
|
569
554
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
555
|
+
const day = this.currentDate;
|
|
556
|
+
const firstOfMonth = new Date(day.getFullYear(), day.getMonth(), 1);
|
|
557
|
+
let dayOfWeek;
|
|
573
558
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
calendarDay.button.setAttribute('tabindex', -1)
|
|
580
|
-
calendarDay.button.classList.remove(this.selectedDayButtonClass)
|
|
581
|
-
const calendarDayDate = calendarDay.date
|
|
582
|
-
calendarDayDate.setHours(0, 0, 0, 0)
|
|
559
|
+
if (this.config.weekStartDay === 'monday') {
|
|
560
|
+
dayOfWeek = firstOfMonth.getDay() === 0 ? 6 : firstOfMonth.getDay() - 1; // Change logic to make Monday first day of week, i.e. 0
|
|
561
|
+
} else {
|
|
562
|
+
dayOfWeek = firstOfMonth.getDay();
|
|
563
|
+
}
|
|
583
564
|
|
|
584
|
-
|
|
585
|
-
today.setHours(0, 0, 0, 0)
|
|
565
|
+
firstOfMonth.setDate(firstOfMonth.getDate() - dayOfWeek);
|
|
586
566
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
567
|
+
const thisDay = new Date(firstOfMonth);
|
|
568
|
+
|
|
569
|
+
// loop through our days
|
|
570
|
+
for (let i = 0; i < this.calendarDays.length; i++) {
|
|
571
|
+
const hidden = thisDay.getMonth() !== day.getMonth();
|
|
572
|
+
const disabled = this.isExcludedDate(thisDay);
|
|
573
|
+
|
|
574
|
+
this.calendarDays[i].update(thisDay, hidden, disabled);
|
|
575
|
+
|
|
576
|
+
thisDay.setDate(thisDay.getDate() + 1);
|
|
596
577
|
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
DatePicker.prototype.setCurrentDate = function (focus = true) {
|
|
581
|
+
const { currentDate } = this;
|
|
582
|
+
this.calendarDays.forEach((calendarDay) => {
|
|
583
|
+
calendarDay.button.classList.add('moj-datepicker__button');
|
|
584
|
+
calendarDay.button.classList.add('moj-datepicker__calendar-day');
|
|
585
|
+
calendarDay.button.setAttribute('tabindex', -1);
|
|
586
|
+
calendarDay.button.classList.remove(this.selectedDayButtonClass);
|
|
587
|
+
const calendarDayDate = calendarDay.date;
|
|
588
|
+
calendarDayDate.setHours(0, 0, 0, 0);
|
|
589
|
+
|
|
590
|
+
const today = new Date();
|
|
591
|
+
today.setHours(0, 0, 0, 0);
|
|
592
|
+
|
|
593
|
+
if (
|
|
594
|
+
calendarDayDate.getTime() ===
|
|
595
|
+
currentDate.getTime() /* && !calendarDay.button.disabled */
|
|
596
|
+
) {
|
|
597
|
+
if (focus) {
|
|
598
|
+
calendarDay.button.setAttribute('tabindex', 0);
|
|
599
|
+
calendarDay.button.focus();
|
|
600
|
+
calendarDay.button.classList.add(this.selectedDayButtonClass);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
597
603
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
604
|
+
if (
|
|
605
|
+
this.inputDate &&
|
|
606
|
+
calendarDayDate.getTime() === this.inputDate.getTime()
|
|
607
|
+
) {
|
|
608
|
+
calendarDay.button.classList.add(this.currentDayButtonClass);
|
|
609
|
+
calendarDay.button.setAttribute('aria-current', 'date');
|
|
610
|
+
} else {
|
|
611
|
+
calendarDay.button.classList.remove(this.currentDayButtonClass);
|
|
612
|
+
calendarDay.button.removeAttribute('aria-current');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (calendarDayDate.getTime() === today.getTime()) {
|
|
616
|
+
calendarDay.button.classList.add(this.todayButtonClass);
|
|
617
|
+
} else {
|
|
618
|
+
calendarDay.button.classList.remove(this.todayButtonClass);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// if no date is tab-able, make the first non-disabled date tab-able
|
|
623
|
+
if (!focus) {
|
|
624
|
+
const enabledDays = this.calendarDays.filter((calendarDay) => {
|
|
625
|
+
return (
|
|
626
|
+
window.getComputedStyle(calendarDay.button).display === 'block' &&
|
|
627
|
+
!calendarDay.button.disabled
|
|
628
|
+
)
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
enabledDays[0].button.setAttribute('tabindex', 0);
|
|
632
|
+
|
|
633
|
+
this.currentDate = enabledDays[0].date;
|
|
607
634
|
}
|
|
635
|
+
};
|
|
608
636
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
calendarDay.button.classList.remove(this.todayButtonClass)
|
|
637
|
+
DatePicker.prototype.selectDate = function (date) {
|
|
638
|
+
if (this.isExcludedDate(date)) {
|
|
639
|
+
return
|
|
613
640
|
}
|
|
614
|
-
})
|
|
615
641
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return (
|
|
620
|
-
window.getComputedStyle(calendarDay.button).display === 'block' &&
|
|
621
|
-
!calendarDay.button.disabled
|
|
622
|
-
)
|
|
623
|
-
})
|
|
642
|
+
this.$calendarButton.querySelector('span').innerText =
|
|
643
|
+
`Choose date. Selected date is ${this.formattedDateHuman(date)}`;
|
|
644
|
+
this.$input.value = this.formattedDateFromDate(date);
|
|
624
645
|
|
|
625
|
-
|
|
646
|
+
const changeEvent = new Event('change', { bubbles: true, cancelable: true });
|
|
647
|
+
this.$input.dispatchEvent(changeEvent);
|
|
626
648
|
|
|
627
|
-
this.
|
|
628
|
-
}
|
|
629
|
-
}
|
|
649
|
+
this.closeDialog();
|
|
650
|
+
};
|
|
630
651
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
652
|
+
DatePicker.prototype.isOpen = function () {
|
|
653
|
+
return this.$dialog.classList.contains('moj-datepicker__dialog--open')
|
|
654
|
+
};
|
|
635
655
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
656
|
+
DatePicker.prototype.toggleDialog = function (event) {
|
|
657
|
+
event.preventDefault();
|
|
658
|
+
if (this.isOpen()) {
|
|
659
|
+
this.closeDialog();
|
|
660
|
+
} else {
|
|
661
|
+
this.setMinAndMaxDatesOnCalendar();
|
|
662
|
+
this.openDialog();
|
|
663
|
+
}
|
|
664
|
+
};
|
|
639
665
|
|
|
640
|
-
|
|
641
|
-
|
|
666
|
+
DatePicker.prototype.openDialog = function () {
|
|
667
|
+
this.$dialog.hidden = false;
|
|
668
|
+
this.$dialog.classList.add('moj-datepicker__dialog--open');
|
|
669
|
+
this.$calendarButton.setAttribute('aria-expanded', 'true');
|
|
642
670
|
|
|
643
|
-
|
|
644
|
-
|
|
671
|
+
// position the dialog
|
|
672
|
+
// if input is wider than dialog pin it to the right
|
|
673
|
+
if (this.$input.offsetWidth > this.$dialog.offsetWidth) {
|
|
674
|
+
this.$dialog.style.right = `0px`;
|
|
675
|
+
}
|
|
676
|
+
this.$dialog.style.top = `${this.$input.offsetHeight + 3}px`;
|
|
645
677
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
678
|
+
// get the date from the input element
|
|
679
|
+
this.inputDate = this.formattedDateFromString(this.$input.value);
|
|
680
|
+
this.currentDate = this.inputDate;
|
|
681
|
+
this.currentDate.setHours(0, 0, 0, 0);
|
|
649
682
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
this.closeDialog()
|
|
654
|
-
} else {
|
|
655
|
-
this.setMinAndMaxDatesOnCalendar()
|
|
656
|
-
this.openDialog()
|
|
657
|
-
}
|
|
658
|
-
}
|
|
683
|
+
this.updateCalendar();
|
|
684
|
+
this.setCurrentDate();
|
|
685
|
+
};
|
|
659
686
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
687
|
+
DatePicker.prototype.closeDialog = function () {
|
|
688
|
+
this.$dialog.hidden = true;
|
|
689
|
+
this.$dialog.classList.remove('moj-datepicker__dialog--open');
|
|
690
|
+
this.$calendarButton.setAttribute('aria-expanded', 'false');
|
|
691
|
+
this.$calendarButton.focus();
|
|
692
|
+
};
|
|
664
693
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
this.$dialog.style.right = `0px`
|
|
669
|
-
}
|
|
670
|
-
this.$dialog.style.top = `${this.$input.offsetHeight + 3}px`
|
|
671
|
-
|
|
672
|
-
// get the date from the input element
|
|
673
|
-
this.inputDate = this.formattedDateFromString(this.$input.value)
|
|
674
|
-
this.currentDate = this.inputDate
|
|
675
|
-
this.currentDate.setHours(0, 0, 0, 0)
|
|
676
|
-
|
|
677
|
-
this.updateCalendar()
|
|
678
|
-
this.setCurrentDate()
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
Datepicker.prototype.closeDialog = function () {
|
|
682
|
-
this.$dialog.hidden = true
|
|
683
|
-
this.$dialog.classList.remove('moj-datepicker__dialog--open')
|
|
684
|
-
this.$calendarButton.setAttribute('aria-expanded', 'false')
|
|
685
|
-
this.$calendarButton.focus()
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
Datepicker.prototype.goToDate = function (date, focus) {
|
|
689
|
-
const current = this.currentDate
|
|
690
|
-
this.currentDate = date
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
current.getMonth() !== this.currentDate.getMonth() ||
|
|
694
|
-
current.getFullYear() !== this.currentDate.getFullYear()
|
|
695
|
-
) {
|
|
696
|
-
this.updateCalendar()
|
|
697
|
-
}
|
|
694
|
+
DatePicker.prototype.goToDate = function (date, focus) {
|
|
695
|
+
const current = this.currentDate;
|
|
696
|
+
this.currentDate = date;
|
|
698
697
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const date = new Date(this.currentDate)
|
|
705
|
-
date.setDate(date.getDate() + 1)
|
|
706
|
-
this.goToDate(date)
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
Datepicker.prototype.focusPreviousDay = function () {
|
|
710
|
-
const date = new Date(this.currentDate)
|
|
711
|
-
date.setDate(date.getDate() - 1)
|
|
712
|
-
this.goToDate(date)
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// week navigation
|
|
716
|
-
Datepicker.prototype.focusNextWeek = function () {
|
|
717
|
-
const date = new Date(this.currentDate)
|
|
718
|
-
date.setDate(date.getDate() + 7)
|
|
719
|
-
this.goToDate(date)
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
Datepicker.prototype.focusPreviousWeek = function () {
|
|
723
|
-
const date = new Date(this.currentDate)
|
|
724
|
-
date.setDate(date.getDate() - 7)
|
|
725
|
-
this.goToDate(date)
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
Datepicker.prototype.focusFirstDayOfWeek = function () {
|
|
729
|
-
const date = new Date(this.currentDate)
|
|
730
|
-
const firstDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 0 : 1
|
|
731
|
-
const dayOfWeek = date.getDay()
|
|
732
|
-
const diff =
|
|
733
|
-
dayOfWeek >= firstDayOfWeekIndex
|
|
734
|
-
? dayOfWeek - firstDayOfWeekIndex
|
|
735
|
-
: 6 - dayOfWeek
|
|
736
|
-
|
|
737
|
-
date.setDate(date.getDate() - diff)
|
|
738
|
-
date.setHours(0, 0, 0, 0)
|
|
739
|
-
|
|
740
|
-
this.goToDate(date)
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
Datepicker.prototype.focusLastDayOfWeek = function () {
|
|
744
|
-
const date = new Date(this.currentDate)
|
|
745
|
-
const lastDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 6 : 0
|
|
746
|
-
const dayOfWeek = date.getDay()
|
|
747
|
-
const diff =
|
|
748
|
-
dayOfWeek <= lastDayOfWeekIndex
|
|
749
|
-
? lastDayOfWeekIndex - dayOfWeek
|
|
750
|
-
: 7 - dayOfWeek
|
|
751
|
-
|
|
752
|
-
date.setDate(date.getDate() + diff)
|
|
753
|
-
date.setHours(0, 0, 0, 0)
|
|
754
|
-
|
|
755
|
-
this.goToDate(date)
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// month navigation
|
|
759
|
-
Datepicker.prototype.focusNextMonth = function (event, focus = true) {
|
|
760
|
-
event.preventDefault()
|
|
761
|
-
const date = new Date(this.currentDate)
|
|
762
|
-
date.setMonth(date.getMonth() + 1, 1)
|
|
763
|
-
this.goToDate(date, focus)
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
Datepicker.prototype.focusPreviousMonth = function (event, focus = true) {
|
|
767
|
-
event.preventDefault()
|
|
768
|
-
const date = new Date(this.currentDate)
|
|
769
|
-
date.setMonth(date.getMonth() - 1, 1)
|
|
770
|
-
this.goToDate(date, focus)
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// year navigation
|
|
774
|
-
Datepicker.prototype.focusNextYear = function (event, focus = true) {
|
|
775
|
-
event.preventDefault()
|
|
776
|
-
const date = new Date(this.currentDate)
|
|
777
|
-
date.setFullYear(date.getFullYear() + 1, date.getMonth(), 1)
|
|
778
|
-
this.goToDate(date, focus)
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
Datepicker.prototype.focusPreviousYear = function (event, focus = true) {
|
|
782
|
-
event.preventDefault()
|
|
783
|
-
const date = new Date(this.currentDate)
|
|
784
|
-
date.setFullYear(date.getFullYear() - 1, date.getMonth(), 1)
|
|
785
|
-
this.goToDate(date, focus)
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* Parse dataset
|
|
790
|
-
*
|
|
791
|
-
* @param {Schema} schema - Component class
|
|
792
|
-
* @param {DOMStringMap} dataset - HTML element dataset
|
|
793
|
-
* @returns {object} Normalised dataset
|
|
794
|
-
*/
|
|
795
|
-
Datepicker.prototype.parseDataset = function (schema, dataset) {
|
|
796
|
-
const parsed = {}
|
|
797
|
-
|
|
798
|
-
for (const [field, ,] of Object.entries(schema.properties)) {
|
|
799
|
-
if (field in dataset) {
|
|
800
|
-
parsed[field] = dataset[field]
|
|
698
|
+
if (
|
|
699
|
+
current.getMonth() !== this.currentDate.getMonth() ||
|
|
700
|
+
current.getFullYear() !== this.currentDate.getFullYear()
|
|
701
|
+
) {
|
|
702
|
+
this.updateCalendar();
|
|
801
703
|
}
|
|
802
|
-
}
|
|
803
704
|
|
|
804
|
-
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
705
|
+
this.setCurrentDate(focus);
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// day navigation
|
|
709
|
+
DatePicker.prototype.focusNextDay = function () {
|
|
710
|
+
const date = new Date(this.currentDate);
|
|
711
|
+
date.setDate(date.getDate() + 1);
|
|
712
|
+
this.goToDate(date);
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
DatePicker.prototype.focusPreviousDay = function () {
|
|
716
|
+
const date = new Date(this.currentDate);
|
|
717
|
+
date.setDate(date.getDate() - 1);
|
|
718
|
+
this.goToDate(date);
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// week navigation
|
|
722
|
+
DatePicker.prototype.focusNextWeek = function () {
|
|
723
|
+
const date = new Date(this.currentDate);
|
|
724
|
+
date.setDate(date.getDate() + 7);
|
|
725
|
+
this.goToDate(date);
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
DatePicker.prototype.focusPreviousWeek = function () {
|
|
729
|
+
const date = new Date(this.currentDate);
|
|
730
|
+
date.setDate(date.getDate() - 7);
|
|
731
|
+
this.goToDate(date);
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
DatePicker.prototype.focusFirstDayOfWeek = function () {
|
|
735
|
+
const date = new Date(this.currentDate);
|
|
736
|
+
const firstDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 0 : 1;
|
|
737
|
+
const dayOfWeek = date.getDay();
|
|
738
|
+
const diff =
|
|
739
|
+
dayOfWeek >= firstDayOfWeekIndex
|
|
740
|
+
? dayOfWeek - firstDayOfWeekIndex
|
|
741
|
+
: 6 - dayOfWeek;
|
|
742
|
+
|
|
743
|
+
date.setDate(date.getDate() - diff);
|
|
744
|
+
date.setHours(0, 0, 0, 0);
|
|
745
|
+
|
|
746
|
+
this.goToDate(date);
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
DatePicker.prototype.focusLastDayOfWeek = function () {
|
|
750
|
+
const date = new Date(this.currentDate);
|
|
751
|
+
const lastDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 6 : 0;
|
|
752
|
+
const dayOfWeek = date.getDay();
|
|
753
|
+
const diff =
|
|
754
|
+
dayOfWeek <= lastDayOfWeekIndex
|
|
755
|
+
? lastDayOfWeekIndex - dayOfWeek
|
|
756
|
+
: 7 - dayOfWeek;
|
|
757
|
+
|
|
758
|
+
date.setDate(date.getDate() + diff);
|
|
759
|
+
date.setHours(0, 0, 0, 0);
|
|
760
|
+
|
|
761
|
+
this.goToDate(date);
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// month navigation
|
|
765
|
+
DatePicker.prototype.focusNextMonth = function (event, focus = true) {
|
|
766
|
+
event.preventDefault();
|
|
767
|
+
const date = new Date(this.currentDate);
|
|
768
|
+
date.setMonth(date.getMonth() + 1, 1);
|
|
769
|
+
this.goToDate(date, focus);
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
DatePicker.prototype.focusPreviousMonth = function (event, focus = true) {
|
|
773
|
+
event.preventDefault();
|
|
774
|
+
const date = new Date(this.currentDate);
|
|
775
|
+
date.setMonth(date.getMonth() - 1, 1);
|
|
776
|
+
this.goToDate(date, focus);
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
// year navigation
|
|
780
|
+
DatePicker.prototype.focusNextYear = function (event, focus = true) {
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
const date = new Date(this.currentDate);
|
|
783
|
+
date.setFullYear(date.getFullYear() + 1, date.getMonth(), 1);
|
|
784
|
+
this.goToDate(date, focus);
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
DatePicker.prototype.focusPreviousYear = function (event, focus = true) {
|
|
788
|
+
event.preventDefault();
|
|
789
|
+
const date = new Date(this.currentDate);
|
|
790
|
+
date.setFullYear(date.getFullYear() - 1, date.getMonth(), 1);
|
|
791
|
+
this.goToDate(date, focus);
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Parse dataset
|
|
796
|
+
*
|
|
797
|
+
* @param {Schema} schema - Component class
|
|
798
|
+
* @param {DOMStringMap} dataset - HTML element dataset
|
|
799
|
+
* @returns {object} Normalised dataset
|
|
800
|
+
*/
|
|
801
|
+
DatePicker.prototype.parseDataset = function (schema, dataset) {
|
|
802
|
+
const parsed = {};
|
|
803
|
+
|
|
804
|
+
for (const [field, ,] of Object.entries(schema.properties)) {
|
|
805
|
+
if (field in dataset) {
|
|
806
|
+
parsed[field] = dataset[field];
|
|
833
807
|
}
|
|
834
808
|
}
|
|
835
|
-
}
|
|
836
809
|
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
DSCalendarDay.prototype.update = function (day, hidden, disabled) {
|
|
870
|
-
const label = day.getDate()
|
|
871
|
-
let accessibleLabel = this.picker.formattedDateHuman(day)
|
|
872
|
-
|
|
873
|
-
if (disabled) {
|
|
874
|
-
this.button.setAttribute('aria-disabled', true)
|
|
875
|
-
accessibleLabel = `Excluded date, ${accessibleLabel}`
|
|
876
|
-
} else {
|
|
877
|
-
this.button.removeAttribute('aria-disabled')
|
|
878
|
-
}
|
|
810
|
+
return parsed
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Config merging function
|
|
815
|
+
*
|
|
816
|
+
* Takes any number of objects and combines them together, with
|
|
817
|
+
* greatest priority on the LAST item passed in.
|
|
818
|
+
*
|
|
819
|
+
* @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
|
|
820
|
+
* @returns {{ [key: string]: unknown }} A merged config object
|
|
821
|
+
*/
|
|
822
|
+
DatePicker.prototype.mergeConfigs = function (...configObjects) {
|
|
823
|
+
const formattedConfigObject = {};
|
|
824
|
+
|
|
825
|
+
// Loop through each of the passed objects
|
|
826
|
+
for (const configObject of configObjects) {
|
|
827
|
+
for (const key of Object.keys(configObject)) {
|
|
828
|
+
const option = formattedConfigObject[key];
|
|
829
|
+
const override = configObject[key];
|
|
830
|
+
|
|
831
|
+
// Push their keys one-by-one into formattedConfigObject. Any duplicate
|
|
832
|
+
// keys with object values will be merged, otherwise the new value will
|
|
833
|
+
// override the existing value.
|
|
834
|
+
if (typeof option === 'object' && typeof override === 'object') {
|
|
835
|
+
// @ts-expect-error Index signature for type 'string' is missing
|
|
836
|
+
formattedConfigObject[key] = this.mergeConfigs(option, override);
|
|
837
|
+
} else {
|
|
838
|
+
formattedConfigObject[key] = override;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
879
842
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
DSCalendarDay.prototype.
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
this.picker.focusLastDayOfWeek()
|
|
923
|
-
break
|
|
924
|
-
case 'PageUp':
|
|
925
|
-
// eslint-disable-next-line no-unused-expressions
|
|
926
|
-
event.shiftKey
|
|
927
|
-
? this.picker.focusPreviousYear(event)
|
|
928
|
-
: this.picker.focusPreviousMonth(event)
|
|
929
|
-
break
|
|
930
|
-
case 'PageDown':
|
|
931
|
-
// eslint-disable-next-line no-unused-expressions
|
|
932
|
-
event.shiftKey
|
|
933
|
-
? this.picker.focusNextYear(event)
|
|
934
|
-
: this.picker.focusNextMonth(event)
|
|
935
|
-
break
|
|
936
|
-
default:
|
|
937
|
-
calendarNavKey = false
|
|
938
|
-
break
|
|
939
|
-
}
|
|
843
|
+
return formattedConfigObject
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
*
|
|
848
|
+
* @param {HTMLElement} button
|
|
849
|
+
* @param {number} index
|
|
850
|
+
* @param {number} row
|
|
851
|
+
* @param {number} column
|
|
852
|
+
* @param {DatePicker} picker
|
|
853
|
+
* @class
|
|
854
|
+
*/
|
|
855
|
+
function DSCalendarDay(button, index, row, column, picker) {
|
|
856
|
+
this.index = index;
|
|
857
|
+
this.row = row;
|
|
858
|
+
this.column = column;
|
|
859
|
+
this.button = button;
|
|
860
|
+
this.picker = picker;
|
|
861
|
+
|
|
862
|
+
this.date = new Date();
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
DSCalendarDay.prototype.init = function () {
|
|
866
|
+
this.button.addEventListener('keydown', this.keyPress.bind(this));
|
|
867
|
+
this.button.addEventListener('click', this.click.bind(this));
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* @param {Date} day - the Date for the calendar day
|
|
872
|
+
* @param {boolean} hidden - visibility of the day
|
|
873
|
+
* @param {boolean} disabled - is the day selectable or excluded
|
|
874
|
+
*/
|
|
875
|
+
DSCalendarDay.prototype.update = function (day, hidden, disabled) {
|
|
876
|
+
const label = day.getDate();
|
|
877
|
+
let accessibleLabel = this.picker.formattedDateHuman(day);
|
|
878
|
+
|
|
879
|
+
if (disabled) {
|
|
880
|
+
this.button.setAttribute('aria-disabled', true);
|
|
881
|
+
accessibleLabel = `Excluded date, ${accessibleLabel}`;
|
|
882
|
+
} else {
|
|
883
|
+
this.button.removeAttribute('aria-disabled');
|
|
884
|
+
}
|
|
940
885
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
886
|
+
if (hidden) {
|
|
887
|
+
this.button.style.display = 'none';
|
|
888
|
+
} else {
|
|
889
|
+
this.button.style.display = 'block';
|
|
890
|
+
}
|
|
891
|
+
this.button.setAttribute(
|
|
892
|
+
'data-testid',
|
|
893
|
+
this.picker.formattedDateFromDate(day)
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
this.button.innerHTML = `<span class="govuk-visually-hidden">${accessibleLabel}</span><span aria-hidden="true">${label}</span>`;
|
|
897
|
+
this.date = new Date(day);
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
DSCalendarDay.prototype.click = function (event) {
|
|
901
|
+
this.picker.goToDate(this.date);
|
|
902
|
+
this.picker.selectDate(this.date);
|
|
903
|
+
|
|
904
|
+
event.stopPropagation();
|
|
905
|
+
event.preventDefault();
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
DSCalendarDay.prototype.keyPress = function (event) {
|
|
909
|
+
let calendarNavKey = true;
|
|
910
|
+
|
|
911
|
+
switch (event.key) {
|
|
912
|
+
case 'ArrowLeft':
|
|
913
|
+
this.picker.focusPreviousDay();
|
|
914
|
+
break
|
|
915
|
+
case 'ArrowRight':
|
|
916
|
+
this.picker.focusNextDay();
|
|
917
|
+
break
|
|
918
|
+
case 'ArrowUp':
|
|
919
|
+
this.picker.focusPreviousWeek();
|
|
920
|
+
break
|
|
921
|
+
case 'ArrowDown':
|
|
922
|
+
this.picker.focusNextWeek();
|
|
923
|
+
break
|
|
924
|
+
case 'Home':
|
|
925
|
+
this.picker.focusFirstDayOfWeek();
|
|
926
|
+
break
|
|
927
|
+
case 'End':
|
|
928
|
+
this.picker.focusLastDayOfWeek();
|
|
929
|
+
break
|
|
930
|
+
case 'PageUp':
|
|
931
|
+
// eslint-disable-next-line no-unused-expressions
|
|
932
|
+
event.shiftKey
|
|
933
|
+
? this.picker.focusPreviousYear(event)
|
|
934
|
+
: this.picker.focusPreviousMonth(event);
|
|
935
|
+
break
|
|
936
|
+
case 'PageDown':
|
|
937
|
+
// eslint-disable-next-line no-unused-expressions
|
|
938
|
+
event.shiftKey
|
|
939
|
+
? this.picker.focusNextYear(event)
|
|
940
|
+
: this.picker.focusNextMonth(event);
|
|
941
|
+
break
|
|
942
|
+
default:
|
|
943
|
+
calendarNavKey = false;
|
|
944
|
+
break
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (calendarNavKey) {
|
|
948
|
+
event.preventDefault();
|
|
949
|
+
event.stopPropagation();
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Schema for component config
|
|
955
|
+
*
|
|
956
|
+
* @typedef {object} Schema
|
|
957
|
+
* @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
|
|
958
|
+
*/
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Schema property for component config
|
|
962
|
+
*
|
|
963
|
+
* @typedef {object} SchemaProperty
|
|
964
|
+
* @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
|
|
965
|
+
*/
|
|
966
|
+
|
|
967
|
+
exports.DatePicker = DatePicker;
|
|
968
|
+
|
|
969
|
+
}));
|