@ministryofjustice/frontend 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/govuk-prototype-kit.config.json +19 -4
  2. package/moj/_base.scss +2 -0
  3. package/moj/_base.scss.map +1 -0
  4. package/moj/all.bundle.js +2523 -0
  5. package/moj/all.bundle.js.map +1 -0
  6. package/moj/all.bundle.mjs +2502 -0
  7. package/moj/all.bundle.mjs.map +1 -0
  8. package/moj/all.mjs +59 -69
  9. package/moj/all.mjs.map +1 -1
  10. package/moj/all.scss +2 -0
  11. package/moj/all.scss.map +1 -0
  12. package/moj/components/_all.scss +2 -0
  13. package/moj/components/_all.scss.map +1 -0
  14. package/moj/components/action-bar/_action-bar.scss +2 -0
  15. package/moj/components/action-bar/_action-bar.scss.map +1 -0
  16. package/moj/components/add-another/_add-another.scss +2 -0
  17. package/moj/components/add-another/_add-another.scss.map +1 -0
  18. package/moj/components/add-another/add-another.bundle.js +128 -0
  19. package/moj/components/add-another/add-another.bundle.js.map +1 -0
  20. package/moj/components/add-another/add-another.bundle.mjs +120 -0
  21. package/moj/components/add-another/add-another.bundle.mjs.map +1 -0
  22. package/moj/components/add-another/add-another.mjs +112 -99
  23. package/moj/components/add-another/add-another.mjs.map +1 -1
  24. package/moj/components/alert/_alert.scss +4 -0
  25. package/moj/components/alert/_alert.scss.map +1 -0
  26. package/moj/components/alert/alert.bundle.js +330 -0
  27. package/moj/components/alert/alert.bundle.js.map +1 -0
  28. package/moj/components/alert/alert.bundle.mjs +322 -0
  29. package/moj/components/alert/alert.bundle.mjs.map +1 -0
  30. package/moj/components/alert/alert.mjs +181 -217
  31. package/moj/components/alert/alert.mjs.map +1 -1
  32. package/moj/components/alert/{alert.spec.helper.js → alert.spec.helper.bundle.js} +1 -1
  33. package/moj/components/alert/alert.spec.helper.bundle.js.map +1 -0
  34. package/moj/components/alert/alert.spec.helper.bundle.mjs +67 -0
  35. package/moj/components/alert/alert.spec.helper.bundle.mjs.map +1 -0
  36. package/moj/components/alert/alert.spec.helper.mjs.map +1 -1
  37. package/moj/components/badge/_badge.scss +2 -0
  38. package/moj/components/badge/_badge.scss.map +1 -0
  39. package/moj/components/banner/_banner.scss +2 -0
  40. package/moj/components/banner/_banner.scss.map +1 -0
  41. package/moj/components/button-menu/README.md +10 -6
  42. package/moj/components/button-menu/_button-menu.scss +4 -1
  43. package/moj/components/button-menu/_button-menu.scss.map +1 -0
  44. package/moj/components/button-menu/button-menu.bundle.js +299 -0
  45. package/moj/components/button-menu/button-menu.bundle.js.map +1 -0
  46. package/moj/components/button-menu/{button-menu.js → button-menu.bundle.mjs} +74 -121
  47. package/moj/components/button-menu/button-menu.bundle.mjs.map +1 -0
  48. package/moj/components/button-menu/button-menu.mjs +246 -285
  49. package/moj/components/button-menu/button-menu.mjs.map +1 -1
  50. package/moj/components/cookie-banner/_cookie-banner.scss +2 -0
  51. package/moj/components/cookie-banner/_cookie-banner.scss.map +1 -0
  52. package/moj/components/currency-input/_currency-input.scss +2 -0
  53. package/moj/components/currency-input/_currency-input.scss.map +1 -0
  54. package/moj/components/date-picker/_date-picker.scss +2 -0
  55. package/moj/components/date-picker/_date-picker.scss.map +1 -0
  56. package/moj/components/date-picker/date-picker.bundle.js +784 -0
  57. package/moj/components/date-picker/date-picker.bundle.js.map +1 -0
  58. package/moj/components/date-picker/{date-picker.js → date-picker.bundle.mjs} +245 -439
  59. package/moj/components/date-picker/date-picker.bundle.mjs.map +1 -0
  60. package/moj/components/date-picker/date-picker.mjs +654 -840
  61. package/moj/components/date-picker/date-picker.mjs.map +1 -1
  62. package/moj/components/filter/_filter.scss +2 -0
  63. package/moj/components/filter/_filter.scss.map +1 -0
  64. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js +96 -0
  65. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js.map +1 -0
  66. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs +88 -0
  67. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs.map +1 -0
  68. package/moj/components/filter-toggle-button/filter-toggle-button.mjs +78 -84
  69. package/moj/components/filter-toggle-button/filter-toggle-button.mjs.map +1 -1
  70. package/moj/components/form-validator/form-validator.bundle.js +198 -0
  71. package/moj/components/form-validator/form-validator.bundle.js.map +1 -0
  72. package/moj/components/form-validator/form-validator.bundle.mjs +190 -0
  73. package/moj/components/form-validator/form-validator.bundle.mjs.map +1 -0
  74. package/moj/components/form-validator/form-validator.mjs +149 -152
  75. package/moj/components/form-validator/form-validator.mjs.map +1 -1
  76. package/moj/components/header/_header.scss +2 -0
  77. package/moj/components/header/_header.scss.map +1 -0
  78. package/moj/components/identity-bar/_identity-bar.scss +2 -0
  79. package/moj/components/identity-bar/_identity-bar.scss.map +1 -0
  80. package/moj/components/interruption-card/_interruption-card.scss +2 -0
  81. package/moj/components/interruption-card/_interruption-card.scss.map +1 -0
  82. package/moj/components/messages/_messages.scss +2 -0
  83. package/moj/components/messages/_messages.scss.map +1 -0
  84. package/moj/components/multi-file-upload/_multi-file-upload.scss +2 -0
  85. package/moj/components/multi-file-upload/_multi-file-upload.scss.map +1 -0
  86. package/moj/components/multi-file-upload/multi-file-upload.bundle.js +223 -0
  87. package/moj/components/multi-file-upload/multi-file-upload.bundle.js.map +1 -0
  88. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs +215 -0
  89. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs.map +1 -0
  90. package/moj/components/multi-file-upload/multi-file-upload.mjs +193 -209
  91. package/moj/components/multi-file-upload/multi-file-upload.mjs.map +1 -1
  92. package/moj/components/multi-select/_multi-select.scss +2 -0
  93. package/moj/components/multi-select/_multi-select.scss.map +1 -0
  94. package/moj/components/multi-select/multi-select.bundle.js +78 -0
  95. package/moj/components/multi-select/multi-select.bundle.js.map +1 -0
  96. package/moj/components/multi-select/multi-select.bundle.mjs +70 -0
  97. package/moj/components/multi-select/multi-select.bundle.mjs.map +1 -0
  98. package/moj/components/multi-select/multi-select.mjs +59 -67
  99. package/moj/components/multi-select/multi-select.mjs.map +1 -1
  100. package/moj/components/notification-badge/_notification-badge.scss +2 -0
  101. package/moj/components/notification-badge/_notification-badge.scss.map +1 -0
  102. package/moj/components/organisation-switcher/_organisation-switcher.scss +2 -0
  103. package/moj/components/organisation-switcher/_organisation-switcher.scss.map +1 -0
  104. package/moj/components/page-header-actions/_page-header-actions.scss +2 -0
  105. package/moj/components/page-header-actions/_page-header-actions.scss.map +1 -0
  106. package/moj/components/pagination/_pagination.scss +2 -2
  107. package/moj/components/pagination/_pagination.scss.map +1 -0
  108. package/moj/components/password-reveal/_password-reveal.scss +2 -0
  109. package/moj/components/password-reveal/_password-reveal.scss.map +1 -0
  110. package/moj/components/password-reveal/password-reveal.bundle.js +49 -0
  111. package/moj/components/password-reveal/password-reveal.bundle.js.map +1 -0
  112. package/moj/components/password-reveal/password-reveal.bundle.mjs +41 -0
  113. package/moj/components/password-reveal/password-reveal.bundle.mjs.map +1 -0
  114. package/moj/components/password-reveal/password-reveal.mjs +36 -31
  115. package/moj/components/password-reveal/password-reveal.mjs.map +1 -1
  116. package/moj/components/primary-navigation/_primary-navigation.scss +2 -0
  117. package/moj/components/primary-navigation/_primary-navigation.scss.map +1 -0
  118. package/moj/components/progress-bar/_progress-bar.scss +2 -0
  119. package/moj/components/progress-bar/_progress-bar.scss.map +1 -0
  120. package/moj/components/rich-text-editor/README.md +15 -9
  121. package/moj/components/rich-text-editor/_rich-text-editor.scss +2 -0
  122. package/moj/components/rich-text-editor/_rich-text-editor.scss.map +1 -0
  123. package/moj/components/rich-text-editor/rich-text-editor.bundle.js +145 -0
  124. package/moj/components/rich-text-editor/rich-text-editor.bundle.js.map +1 -0
  125. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs +137 -0
  126. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs.map +1 -0
  127. package/moj/components/rich-text-editor/rich-text-editor.mjs +124 -145
  128. package/moj/components/rich-text-editor/rich-text-editor.mjs.map +1 -1
  129. package/moj/components/search/_search.scss +2 -0
  130. package/moj/components/search/_search.scss.map +1 -0
  131. package/moj/components/search-toggle/{search-toggle.scss → _search-toggle.scss} +2 -0
  132. package/moj/components/search-toggle/_search-toggle.scss.map +1 -0
  133. package/moj/components/search-toggle/search-toggle.bundle.js +54 -0
  134. package/moj/components/search-toggle/search-toggle.bundle.js.map +1 -0
  135. package/moj/components/search-toggle/search-toggle.bundle.mjs +46 -0
  136. package/moj/components/search-toggle/search-toggle.bundle.mjs.map +1 -0
  137. package/moj/components/search-toggle/search-toggle.mjs +40 -49
  138. package/moj/components/search-toggle/search-toggle.mjs.map +1 -1
  139. package/moj/components/side-navigation/_side-navigation.scss +2 -0
  140. package/moj/components/side-navigation/_side-navigation.scss.map +1 -0
  141. package/moj/components/sortable-table/_sortable-table.scss +2 -2
  142. package/moj/components/sortable-table/_sortable-table.scss.map +1 -0
  143. package/moj/components/sortable-table/sortable-table.bundle.js +134 -0
  144. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -0
  145. package/moj/components/sortable-table/sortable-table.bundle.mjs +126 -0
  146. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -0
  147. package/moj/components/sortable-table/sortable-table.mjs +117 -130
  148. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  149. package/moj/components/sub-navigation/_sub-navigation.scss +2 -0
  150. package/moj/components/sub-navigation/_sub-navigation.scss.map +1 -0
  151. package/moj/components/tag/_tag.scss +2 -0
  152. package/moj/components/tag/_tag.scss.map +1 -0
  153. package/moj/components/task-list/_task-list.scss +2 -0
  154. package/moj/components/task-list/_task-list.scss.map +1 -0
  155. package/moj/components/ticket-panel/_ticket-panel.scss +2 -0
  156. package/moj/components/ticket-panel/_ticket-panel.scss.map +1 -0
  157. package/moj/components/timeline/_timeline.scss +2 -0
  158. package/moj/components/timeline/_timeline.scss.map +1 -0
  159. package/moj/filters/all.js +44 -22
  160. package/moj/helpers/_all.scss +2 -0
  161. package/moj/helpers/_all.scss.map +1 -0
  162. package/moj/helpers/_hidden.scss +2 -0
  163. package/moj/helpers/_hidden.scss.map +1 -0
  164. package/moj/helpers/_links.scss +2 -0
  165. package/moj/helpers/_links.scss.map +1 -0
  166. package/moj/{helpers.js → helpers.bundle.js} +37 -42
  167. package/moj/helpers.bundle.js.map +1 -0
  168. package/moj/helpers.bundle.mjs +179 -0
  169. package/moj/helpers.bundle.mjs.map +1 -0
  170. package/moj/helpers.mjs +52 -28
  171. package/moj/helpers.mjs.map +1 -1
  172. package/moj/init.js +11 -2
  173. package/moj/moj-frontend.min.css +1 -1
  174. package/moj/moj-frontend.min.css.map +1 -1
  175. package/moj/moj-frontend.min.js +1 -1
  176. package/moj/moj-frontend.min.js.map +1 -1
  177. package/moj/objects/_all.scss +2 -0
  178. package/moj/objects/_all.scss.map +1 -0
  179. package/moj/objects/_button-group.scss +2 -0
  180. package/moj/objects/_button-group.scss.map +1 -0
  181. package/moj/objects/_filter-layout.scss +2 -0
  182. package/moj/objects/_filter-layout.scss.map +1 -0
  183. package/moj/objects/_scrollable-pane.scss +2 -0
  184. package/moj/objects/_scrollable-pane.scss.map +1 -0
  185. package/moj/objects/_width-container.scss +2 -0
  186. package/moj/objects/_width-container.scss.map +1 -0
  187. package/moj/settings/_all.scss +2 -0
  188. package/moj/settings/_all.scss.map +1 -0
  189. package/moj/settings/_assets.scss +2 -0
  190. package/moj/settings/_assets.scss.map +1 -0
  191. package/moj/settings/_colours.scss +2 -0
  192. package/moj/settings/_colours.scss.map +1 -0
  193. package/moj/settings/_measurements.scss +2 -0
  194. package/moj/settings/_measurements.scss.map +1 -0
  195. package/moj/settings/_typography.scss +2 -0
  196. package/moj/settings/_typography.scss.map +1 -0
  197. package/moj/template.njk +13 -0
  198. package/moj/utilities/_all.scss +2 -0
  199. package/moj/utilities/_all.scss.map +1 -0
  200. package/moj/utilities/_hidden.scss +2 -0
  201. package/moj/utilities/_hidden.scss.map +1 -0
  202. package/moj/utilities/_width-container.scss +2 -0
  203. package/moj/utilities/_width-container.scss.map +1 -0
  204. package/moj/vendor/govuk-frontend/_base.scss +2 -0
  205. package/moj/vendor/govuk-frontend/_base.scss.map +1 -0
  206. package/moj/vendor/govuk-frontend/_index.scss +2 -0
  207. package/moj/vendor/govuk-frontend/_index.scss.map +1 -0
  208. package/moj/{version.js → version.bundle.js} +1 -1
  209. package/moj/version.bundle.js.map +1 -0
  210. package/moj/version.bundle.mjs +4 -0
  211. package/moj/version.bundle.mjs.map +1 -0
  212. package/moj/version.mjs.map +1 -1
  213. package/package.json +5 -6
  214. package/moj/all.jquery.min.js +0 -1
  215. package/moj/all.jquery.min.js.map +0 -1
  216. package/moj/all.js +0 -2662
  217. package/moj/all.js.map +0 -1
  218. package/moj/components/add-another/add-another.js +0 -115
  219. package/moj/components/add-another/add-another.js.map +0 -1
  220. package/moj/components/alert/alert.js +0 -356
  221. package/moj/components/alert/alert.js.map +0 -1
  222. package/moj/components/alert/alert.spec.helper.js.map +0 -1
  223. package/moj/components/button-menu/button-menu.js.map +0 -1
  224. package/moj/components/date-picker/date-picker.js.map +0 -1
  225. package/moj/components/filter-toggle-button/filter-toggle-button.js +0 -102
  226. package/moj/components/filter-toggle-button/filter-toggle-button.js.map +0 -1
  227. package/moj/components/form-validator/form-validator.js +0 -205
  228. package/moj/components/form-validator/form-validator.js.map +0 -1
  229. package/moj/components/multi-file-upload/multi-file-upload.js +0 -241
  230. package/moj/components/multi-file-upload/multi-file-upload.js.map +0 -1
  231. package/moj/components/multi-select/multi-select.js +0 -86
  232. package/moj/components/multi-select/multi-select.js.map +0 -1
  233. package/moj/components/password-reveal/password-reveal.js +0 -44
  234. package/moj/components/password-reveal/password-reveal.js.map +0 -1
  235. package/moj/components/rich-text-editor/rich-text-editor.js +0 -166
  236. package/moj/components/rich-text-editor/rich-text-editor.js.map +0 -1
  237. package/moj/components/search-toggle/search-toggle.js +0 -63
  238. package/moj/components/search-toggle/search-toggle.js.map +0 -1
  239. package/moj/components/sortable-table/sortable-table.js +0 -147
  240. package/moj/components/sortable-table/sortable-table.js.map +0 -1
  241. package/moj/helpers.js.map +0 -1
  242. package/moj/vendor/html5shiv.js +0 -326
  243. package/moj/vendor/jquery.js +0 -9300
  244. package/moj/version.js.map +0 -1
@@ -0,0 +1,784 @@
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
+ class DatePicker {
8
+ /**
9
+ * @param {Element | null} $module - HTML element to use for date picker
10
+ * @param {DatePickerConfig} [config] - Date picker config
11
+ */
12
+ constructor($module, config = {}) {
13
+ if (!$module || !($module instanceof HTMLElement)) {
14
+ return this;
15
+ }
16
+ const $input = $module.querySelector('.moj-js-datepicker-input');
17
+
18
+ // Check that required elements are present
19
+ if (!$input || !($input instanceof HTMLInputElement)) {
20
+ return this;
21
+ }
22
+ this.$module = $module;
23
+ this.$input = $input;
24
+ const schema = Object.freeze({
25
+ properties: {
26
+ excludedDates: {
27
+ type: 'string'
28
+ },
29
+ excludedDays: {
30
+ type: 'string'
31
+ },
32
+ leadingZeros: {
33
+ type: 'string'
34
+ },
35
+ maxDate: {
36
+ type: 'string'
37
+ },
38
+ minDate: {
39
+ type: 'string'
40
+ },
41
+ weekStartDay: {
42
+ type: 'string'
43
+ }
44
+ }
45
+ });
46
+ const defaults = {
47
+ leadingZeros: false,
48
+ weekStartDay: 'monday'
49
+ };
50
+
51
+ // data attributes override JS config, which overrides defaults
52
+ this.config = this.mergeConfigs(defaults, config, this.parseDataset(schema, $module.dataset));
53
+ this.dayLabels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
54
+ this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
55
+ this.currentDate = new Date();
56
+ this.currentDate.setHours(0, 0, 0, 0);
57
+ this.calendarDays = [];
58
+ this.excludedDates = [];
59
+ this.excludedDays = [];
60
+ this.buttonClass = 'moj-datepicker__button';
61
+ this.selectedDayButtonClass = 'moj-datepicker__button--selected';
62
+ this.currentDayButtonClass = 'moj-datepicker__button--current';
63
+ this.todayButtonClass = 'moj-datepicker__button--today';
64
+ if (this.$module.dataset.initialized) {
65
+ return this;
66
+ }
67
+ this.setOptions();
68
+ this.initControls();
69
+ this.$module.setAttribute('data-initialized', 'true');
70
+ }
71
+ initControls() {
72
+ this.id = `datepicker-${this.$input.id}`;
73
+ this.$dialog = this.createDialog();
74
+ this.createCalendarHeaders();
75
+ const $componentWrapper = document.createElement('div');
76
+ const $inputWrapper = document.createElement('div');
77
+ $componentWrapper.classList.add('moj-datepicker__wrapper');
78
+ $inputWrapper.classList.add('govuk-input__wrapper');
79
+ this.$input.parentElement.insertBefore($componentWrapper, this.$input);
80
+ $componentWrapper.appendChild($inputWrapper);
81
+ $inputWrapper.appendChild(this.$input);
82
+ $inputWrapper.insertAdjacentHTML('beforeend', this.toggleTemplate());
83
+ $componentWrapper.insertAdjacentElement('beforeend', this.$dialog);
84
+ this.$calendarButton = this.$module.querySelector('.moj-js-datepicker-toggle');
85
+ this.$dialogTitle = this.$dialog.querySelector('.moj-js-datepicker-month-year');
86
+ this.createCalendar();
87
+ this.$prevMonthButton = this.$dialog.querySelector('.moj-js-datepicker-prev-month');
88
+ this.$prevYearButton = this.$dialog.querySelector('.moj-js-datepicker-prev-year');
89
+ this.$nextMonthButton = this.$dialog.querySelector('.moj-js-datepicker-next-month');
90
+ this.$nextYearButton = this.$dialog.querySelector('.moj-js-datepicker-next-year');
91
+ this.$cancelButton = this.$dialog.querySelector('.moj-js-datepicker-cancel');
92
+ this.$okButton = this.$dialog.querySelector('.moj-js-datepicker-ok');
93
+
94
+ // add event listeners
95
+ this.$prevMonthButton.addEventListener('click', event => this.focusPreviousMonth(event, false));
96
+ this.$prevYearButton.addEventListener('click', event => this.focusPreviousYear(event, false));
97
+ this.$nextMonthButton.addEventListener('click', event => this.focusNextMonth(event, false));
98
+ this.$nextYearButton.addEventListener('click', event => this.focusNextYear(event, false));
99
+ this.$cancelButton.addEventListener('click', event => {
100
+ event.preventDefault();
101
+ this.closeDialog();
102
+ });
103
+ this.$okButton.addEventListener('click', () => {
104
+ this.selectDate(this.currentDate);
105
+ });
106
+ const dialogButtons = this.$dialog.querySelectorAll('button:not([disabled="true"])');
107
+ this.$firstButtonInDialog = dialogButtons[0];
108
+ this.$lastButtonInDialog = dialogButtons[dialogButtons.length - 1];
109
+ this.$firstButtonInDialog.addEventListener('keydown', event => this.firstButtonKeydown(event));
110
+ this.$lastButtonInDialog.addEventListener('keydown', event => this.lastButtonKeydown(event));
111
+ this.$calendarButton.addEventListener('click', event => this.toggleDialog(event));
112
+ this.$dialog.addEventListener('keydown', event => {
113
+ if (event.key === 'Escape') {
114
+ this.closeDialog();
115
+ event.preventDefault();
116
+ event.stopPropagation();
117
+ }
118
+ });
119
+ document.body.addEventListener('mouseup', event => this.backgroundClick(event));
120
+
121
+ // populates calendar with initial dates, avoids Wave errors about null buttons
122
+ this.updateCalendar();
123
+ }
124
+ createDialog() {
125
+ const titleId = `datepicker-title-${this.$input.id}`;
126
+ const $dialog = document.createElement('div');
127
+ $dialog.id = this.id;
128
+ $dialog.setAttribute('class', 'moj-datepicker__dialog');
129
+ $dialog.setAttribute('role', 'dialog');
130
+ $dialog.setAttribute('aria-modal', 'true');
131
+ $dialog.setAttribute('aria-labelledby', titleId);
132
+ $dialog.innerHTML = this.dialogTemplate(titleId);
133
+ $dialog.hidden = true;
134
+ return $dialog;
135
+ }
136
+ createCalendar() {
137
+ const $tbody = this.$dialog.querySelector('tbody');
138
+ let dayCount = 0;
139
+ for (let i = 0; i < 6; i++) {
140
+ // create row
141
+ const $row = $tbody.insertRow(i);
142
+ for (let j = 0; j < 7; j++) {
143
+ // create cell (day)
144
+ const $cell = document.createElement('td');
145
+ const $dateButton = document.createElement('button');
146
+ $cell.appendChild($dateButton);
147
+ $row.appendChild($cell);
148
+ const calendarDay = new DSCalendarDay($dateButton, dayCount, i, j, this);
149
+ this.calendarDays.push(calendarDay);
150
+ dayCount++;
151
+ }
152
+ }
153
+ }
154
+ toggleTemplate() {
155
+ return `<button class="moj-datepicker__toggle moj-js-datepicker-toggle" type="button" aria-haspopup="dialog" aria-controls="${this.id}" aria-expanded="false">
156
+ <span class="govuk-visually-hidden">Choose date</span>
157
+ <svg width="32" height="24" focusable="false" class="moj-datepicker-icon" aria-hidden="true" role="img" viewBox="0 0 22 22">
158
+ <path
159
+ fill="currentColor"
160
+ fill-rule="evenodd"
161
+ clip-rule="evenodd"
162
+ d="M16.1333 2.93333H5.86668V4.4C5.86668 5.21002 5.21003 5.86667 4.40002 5.86667C3.59 5.86667 2.93335 5.21002 2.93335 4.4V2.93333H2C0.895431 2.93333 0 3.82877 0 4.93334V19.2667C0 20.3712 0.89543 21.2667 2 21.2667H20C21.1046 21.2667 22 20.3712 22 19.2667V4.93333C22 3.82876 21.1046 2.93333 20 2.93333H19.0667V4.4C19.0667 5.21002 18.41 5.86667 17.6 5.86667C16.79 5.86667 16.1333 5.21002 16.1333 4.4V2.93333ZM20.5333 8.06667H1.46665V18.8C1.46665 19.3523 1.91436 19.8 2.46665 19.8H19.5333C20.0856 19.8 20.5333 19.3523 20.5333 18.8V8.06667Z"
163
+ ></path>
164
+ <rect x="3.66669" width="1.46667" height="5.13333" rx="0.733333" fill="currentColor"></rect>
165
+ <rect x="16.8667" width="1.46667" height="5.13333" rx="0.733333" fill="currentColor"></rect>
166
+ </svg>
167
+ </button>`;
168
+ }
169
+
170
+ /**
171
+ * HTML template for calendar dialog
172
+ *
173
+ * @param {string} [titleId] - Id attribute for dialog title
174
+ * @returns {string}
175
+ */
176
+ dialogTemplate(titleId) {
177
+ return `<div class="moj-datepicker__dialog-header">
178
+ <div class="moj-datepicker__dialog-navbuttons">
179
+ <button class="moj-datepicker__button moj-js-datepicker-prev-year">
180
+ <span class="govuk-visually-hidden">Previous year</span>
181
+ <svg width="44" height="40" viewBox="0 0 44 40" fill="none" fill="none" focusable="false" aria-hidden="true" role="img">
182
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M23.1643 20L28.9572 14.2071L27.5429 12.7929L20.3358 20L27.5429 27.2071L28.9572 25.7929L23.1643 20Z" fill="currentColor"/>
183
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M17.1643 20L22.9572 14.2071L21.5429 12.7929L14.3358 20L21.5429 27.2071L22.9572 25.7929L17.1643 20Z" fill="currentColor"/>
184
+ </svg>
185
+ </button>
186
+
187
+ <button class="moj-datepicker__button moj-js-datepicker-prev-month">
188
+ <span class="govuk-visually-hidden">Previous month</span>
189
+ <svg width="44" height="40" viewBox="0 0 44 40" fill="none" focusable="false" aria-hidden="true" role="img">
190
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M20.5729 20L25.7865 14.2071L24.5137 12.7929L18.0273 20L24.5137 27.2071L25.7865 25.7929L20.5729 20Z" fill="currentColor"/>
191
+ </svg>
192
+ </button>
193
+ </div>
194
+
195
+ <h2 id="${titleId}" class="moj-datepicker__dialog-title moj-js-datepicker-month-year" aria-live="polite">June 2020</h2>
196
+
197
+ <div class="moj-datepicker__dialog-navbuttons">
198
+ <button class="moj-datepicker__button moj-js-datepicker-next-month">
199
+ <span class="govuk-visually-hidden">Next month</span>
200
+ <svg width="44" height="40" viewBox="0 0 44 40" fill="none" focusable="false" aria-hidden="true" role="img">
201
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M23.4271 20L18.2135 14.2071L19.4863 12.7929L25.9727 20L19.4863 27.2071L18.2135 25.7929L23.4271 20Z" fill="currentColor"/>
202
+ </svg>
203
+ </button>
204
+
205
+ <button class="moj-datepicker__button moj-js-datepicker-next-year">
206
+ <span class="govuk-visually-hidden">Next year</span>
207
+ <svg width="44" height="40" viewBox="0 0 44 40" fill="none" fill="none" focusable="false" aria-hidden="true" role="img">
208
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M20.8357 20L15.0428 14.2071L16.4571 12.7929L23.6642 20L16.4571 27.2071L15.0428 25.7929L20.8357 20Z" fill="currentColor"/>
209
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M26.8357 20L21.0428 14.2071L22.4571 12.7929L29.6642 20L22.4571 27.2071L21.0428 25.7929L26.8357 20Z" fill="currentColor"/>
210
+ </svg>
211
+ </button>
212
+ </div>
213
+ </div>
214
+
215
+ <table class="moj-datepicker__calendar moj-js-datepicker-grid" role="grid" aria-labelledby="${titleId}">
216
+ <thead>
217
+ <tr></tr>
218
+ </thead>
219
+
220
+ <tbody></tbody>
221
+ </table>
222
+
223
+ <div class="govuk-button-group">
224
+ <button type="button" class="govuk-button moj-js-datepicker-ok">Select</button>
225
+ <button type="button" class="govuk-button govuk-button--secondary moj-js-datepicker-cancel">Close</button>
226
+ </div>`;
227
+ }
228
+ createCalendarHeaders() {
229
+ this.dayLabels.forEach(day => {
230
+ const html = `<th scope="col"><span aria-hidden="true">${day.substring(0, 3)}</span><span class="govuk-visually-hidden">${day}</span></th>`;
231
+ const $headerRow = this.$dialog.querySelector('thead > tr');
232
+ $headerRow.insertAdjacentHTML('beforeend', html);
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Pads given number with leading zeros
238
+ *
239
+ * @param {number} value - The value to be padded
240
+ * @param {number} length - The length in characters of the output
241
+ * @returns {string}
242
+ */
243
+ leadingZeros(value, length = 2) {
244
+ let ret = value.toString();
245
+ while (ret.length < length) {
246
+ ret = `0${ret}`;
247
+ }
248
+ return ret;
249
+ }
250
+ setOptions() {
251
+ this.setMinAndMaxDatesOnCalendar();
252
+ this.setExcludedDates();
253
+ this.setExcludedDays();
254
+ this.setLeadingZeros();
255
+ this.setWeekStartDay();
256
+ }
257
+ setMinAndMaxDatesOnCalendar() {
258
+ if (this.config.minDate) {
259
+ this.minDate = this.formattedDateFromString(this.config.minDate, null);
260
+ if (this.minDate && this.currentDate < this.minDate) {
261
+ this.currentDate = this.minDate;
262
+ }
263
+ }
264
+ if (this.config.maxDate) {
265
+ this.maxDate = this.formattedDateFromString(this.config.maxDate, null);
266
+ if (this.maxDate && this.currentDate > this.maxDate) {
267
+ this.currentDate = this.maxDate;
268
+ }
269
+ }
270
+ }
271
+ setExcludedDates() {
272
+ if (this.config.excludedDates) {
273
+ this.excludedDates = this.config.excludedDates.replace(/\s+/, ' ').split(' ').map(item => {
274
+ return item.includes('-') ? this.parseDateRangeString(item) : [this.formattedDateFromString(item)];
275
+ }).reduce((dates, items) => dates.concat(items)).filter(date => date);
276
+ }
277
+ }
278
+
279
+ /*
280
+ * Parses a daterange string into an array of dates
281
+ * @param {String} datestring - A daterange string in the format "dd/mm/yyyy-dd/mm/yyyy"
282
+ * @returns {Date[]}
283
+ */
284
+ parseDateRangeString(datestring) {
285
+ const dates = [];
286
+ const [startDate, endDate] = datestring.split('-').map(d => this.formattedDateFromString(d, null));
287
+ if (startDate && endDate) {
288
+ const date = new Date(startDate.getTime());
289
+ /* eslint-disable no-unmodified-loop-condition */
290
+ while (date <= endDate) {
291
+ dates.push(new Date(date));
292
+ date.setDate(date.getDate() + 1);
293
+ }
294
+ /* eslint-enable no-unmodified-loop-condition */
295
+ }
296
+ return dates;
297
+ }
298
+ setExcludedDays() {
299
+ if (this.config.excludedDays) {
300
+ // lowercase and arrange dayLabels to put indexOf sunday == 0 for comparison
301
+ // with getDay() function
302
+ const weekDays = this.dayLabels.map(item => item.toLowerCase());
303
+ if (this.config.weekStartDay === 'monday') {
304
+ weekDays.unshift(weekDays.pop());
305
+ }
306
+ this.excludedDays = this.config.excludedDays.replace(/\s+/, ' ').toLowerCase().split(' ').map(item => weekDays.indexOf(item)).filter(item => item !== -1);
307
+ }
308
+ }
309
+ setLeadingZeros() {
310
+ if (typeof this.config.leadingZeros !== 'boolean') {
311
+ if (this.config.leadingZeros.toLowerCase() === 'true') {
312
+ this.config.leadingZeros = true;
313
+ return;
314
+ }
315
+ if (this.config.leadingZeros.toLowerCase() === 'false') {
316
+ this.config.leadingZeros = false;
317
+ }
318
+ }
319
+ }
320
+ setWeekStartDay() {
321
+ const weekStartDayParam = this.config.weekStartDay;
322
+ if (weekStartDayParam && weekStartDayParam.toLowerCase() === 'sunday') {
323
+ this.config.weekStartDay = 'sunday';
324
+ // Rotate dayLabels array to put Sunday as the first item
325
+ this.dayLabels.unshift(this.dayLabels.pop());
326
+ } else {
327
+ this.config.weekStartDay = 'monday';
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Determine if a date is selecteable
333
+ *
334
+ * @param {Date} date - the date to check
335
+ * @returns {boolean}
336
+ */
337
+ isExcludedDate(date) {
338
+ // This comparison does not work correctly - it will exclude the mindate itself
339
+ // see: https://github.com/ministryofjustice/moj-frontend/issues/923
340
+ if (this.minDate && this.minDate > date) {
341
+ return true;
342
+ }
343
+
344
+ // This comparison works as expected - the maxdate will not be excluded
345
+ if (this.maxDate && this.maxDate < date) {
346
+ return true;
347
+ }
348
+ for (const excludedDate of this.excludedDates) {
349
+ if (date.toDateString() === excludedDate.toDateString()) {
350
+ return true;
351
+ }
352
+ }
353
+ if (this.excludedDays.includes(date.getDay())) {
354
+ return true;
355
+ }
356
+ return false;
357
+ }
358
+
359
+ /**
360
+ * Get a Date object from a string
361
+ *
362
+ * @param {string} dateString - string in the format d/m/yyyy dd/mm/yyyy
363
+ * @param {Date} fallback - date object to return if formatting fails
364
+ * @returns {Date}
365
+ */
366
+ formattedDateFromString(dateString, fallback = new Date()) {
367
+ let formattedDate = null;
368
+ // Accepts d/m/yyyy and dd/mm/yyyy
369
+ const dateFormatPattern = /(\d{1,2})([-/,. ])(\d{1,2})\2(\d{4})/;
370
+ if (!dateFormatPattern.test(dateString)) return fallback;
371
+ const match = dateFormatPattern.exec(dateString);
372
+ const day = match[1];
373
+ const month = match[3];
374
+ const year = match[4];
375
+ formattedDate = new Date(`${year}-${month}-${day}`);
376
+ if (formattedDate instanceof Date && Number.isFinite(formattedDate.getTime())) {
377
+ return formattedDate;
378
+ }
379
+ return fallback;
380
+ }
381
+
382
+ /**
383
+ * Get a formatted date string from a Date object
384
+ *
385
+ * @param {Date} date - date to format to a string
386
+ * @returns {string}
387
+ */
388
+ formattedDateFromDate(date) {
389
+ if (this.config.leadingZeros) {
390
+ return `${this.leadingZeros(date.getDate())}/${this.leadingZeros(date.getMonth() + 1)}/${date.getFullYear()}`;
391
+ }
392
+ return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
393
+ }
394
+
395
+ /**
396
+ * Get a human readable date in the format Monday 2 March 2024
397
+ *
398
+ * @param {Date} date - date to format
399
+ * @returns {string}
400
+ */
401
+ formattedDateHuman(date) {
402
+ return `${this.dayLabels[(date.getDay() + 6) % 7]} ${date.getDate()} ${this.monthLabels[date.getMonth()]} ${date.getFullYear()}`;
403
+ }
404
+ backgroundClick(event) {
405
+ if (this.isOpen() && event.target instanceof Node && !this.$dialog.contains(event.target) && !this.$input.contains(event.target) && !this.$calendarButton.contains(event.target)) {
406
+ event.preventDefault();
407
+ this.closeDialog();
408
+ }
409
+ }
410
+ firstButtonKeydown(event) {
411
+ if (event.key === 'Tab' && event.shiftKey) {
412
+ this.$lastButtonInDialog.focus();
413
+ event.preventDefault();
414
+ }
415
+ }
416
+ lastButtonKeydown(event) {
417
+ if (event.key === 'Tab' && !event.shiftKey) {
418
+ this.$firstButtonInDialog.focus();
419
+ event.preventDefault();
420
+ }
421
+ }
422
+
423
+ // render calendar
424
+ updateCalendar() {
425
+ this.$dialogTitle.innerHTML = `${this.monthLabels[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;
426
+ const day = this.currentDate;
427
+ const firstOfMonth = new Date(day.getFullYear(), day.getMonth(), 1);
428
+ let dayOfWeek;
429
+ if (this.config.weekStartDay === 'monday') {
430
+ dayOfWeek = firstOfMonth.getDay() === 0 ? 6 : firstOfMonth.getDay() - 1; // Change logic to make Monday first day of week, i.e. 0
431
+ } else {
432
+ dayOfWeek = firstOfMonth.getDay();
433
+ }
434
+ firstOfMonth.setDate(firstOfMonth.getDate() - dayOfWeek);
435
+ const thisDay = new Date(firstOfMonth);
436
+
437
+ // loop through our days
438
+ for (const calendarDay of this.calendarDays) {
439
+ const hidden = thisDay.getMonth() !== day.getMonth();
440
+ const disabled = this.isExcludedDate(thisDay);
441
+ calendarDay.update(thisDay, hidden, disabled);
442
+ thisDay.setDate(thisDay.getDate() + 1);
443
+ }
444
+ }
445
+ setCurrentDate(focus = true) {
446
+ const {
447
+ currentDate
448
+ } = this;
449
+ this.calendarDays.forEach(calendarDay => {
450
+ calendarDay.button.classList.add('moj-datepicker__button');
451
+ calendarDay.button.classList.add('moj-datepicker__calendar-day');
452
+ calendarDay.button.setAttribute('tabindex', '-1');
453
+ calendarDay.button.classList.remove(this.selectedDayButtonClass);
454
+ const calendarDayDate = calendarDay.date;
455
+ calendarDayDate.setHours(0, 0, 0, 0);
456
+ const today = new Date();
457
+ today.setHours(0, 0, 0, 0);
458
+ if (calendarDayDate.getTime() === currentDate.getTime() /* && !calendarDay.button.disabled */) {
459
+ if (focus) {
460
+ calendarDay.button.setAttribute('tabindex', '0');
461
+ calendarDay.button.focus();
462
+ calendarDay.button.classList.add(this.selectedDayButtonClass);
463
+ }
464
+ }
465
+ if (this.inputDate && calendarDayDate.getTime() === this.inputDate.getTime()) {
466
+ calendarDay.button.classList.add(this.currentDayButtonClass);
467
+ calendarDay.button.setAttribute('aria-current', 'date');
468
+ } else {
469
+ calendarDay.button.classList.remove(this.currentDayButtonClass);
470
+ calendarDay.button.removeAttribute('aria-current');
471
+ }
472
+ if (calendarDayDate.getTime() === today.getTime()) {
473
+ calendarDay.button.classList.add(this.todayButtonClass);
474
+ } else {
475
+ calendarDay.button.classList.remove(this.todayButtonClass);
476
+ }
477
+ });
478
+
479
+ // if no date is tab-able, make the first non-disabled date tab-able
480
+ if (!focus) {
481
+ const enabledDays = this.calendarDays.filter(calendarDay => {
482
+ return window.getComputedStyle(calendarDay.button).display === 'block' && !calendarDay.button.disabled;
483
+ });
484
+ enabledDays[0].button.setAttribute('tabindex', '0');
485
+ this.currentDate = enabledDays[0].date;
486
+ }
487
+ }
488
+ selectDate(date) {
489
+ if (this.isExcludedDate(date)) {
490
+ return;
491
+ }
492
+ this.$calendarButton.querySelector('span').innerText = `Choose date. Selected date is ${this.formattedDateHuman(date)}`;
493
+ this.$input.value = this.formattedDateFromDate(date);
494
+ const changeEvent = new Event('change', {
495
+ bubbles: true,
496
+ cancelable: true
497
+ });
498
+ this.$input.dispatchEvent(changeEvent);
499
+ this.closeDialog();
500
+ }
501
+ isOpen() {
502
+ return this.$dialog.classList.contains('moj-datepicker__dialog--open');
503
+ }
504
+ toggleDialog(event) {
505
+ event.preventDefault();
506
+ if (this.isOpen()) {
507
+ this.closeDialog();
508
+ } else {
509
+ this.setMinAndMaxDatesOnCalendar();
510
+ this.openDialog();
511
+ }
512
+ }
513
+ openDialog() {
514
+ this.$dialog.hidden = false;
515
+ this.$dialog.classList.add('moj-datepicker__dialog--open');
516
+ this.$calendarButton.setAttribute('aria-expanded', 'true');
517
+
518
+ // position the dialog
519
+ // if input is wider than dialog pin it to the right
520
+ if (this.$input.offsetWidth > this.$dialog.offsetWidth) {
521
+ this.$dialog.style.right = `0px`;
522
+ }
523
+ this.$dialog.style.top = `${this.$input.offsetHeight + 3}px`;
524
+
525
+ // get the date from the input element
526
+ this.inputDate = this.formattedDateFromString(this.$input.value);
527
+ this.currentDate = this.inputDate;
528
+ this.currentDate.setHours(0, 0, 0, 0);
529
+ this.updateCalendar();
530
+ this.setCurrentDate();
531
+ }
532
+ closeDialog() {
533
+ this.$dialog.hidden = true;
534
+ this.$dialog.classList.remove('moj-datepicker__dialog--open');
535
+ this.$calendarButton.setAttribute('aria-expanded', 'false');
536
+ this.$calendarButton.focus();
537
+ }
538
+ goToDate(date, focus) {
539
+ const current = this.currentDate;
540
+ this.currentDate = date;
541
+ if (current.getMonth() !== this.currentDate.getMonth() || current.getFullYear() !== this.currentDate.getFullYear()) {
542
+ this.updateCalendar();
543
+ }
544
+ this.setCurrentDate(focus);
545
+ }
546
+
547
+ // day navigation
548
+ focusNextDay() {
549
+ const date = new Date(this.currentDate);
550
+ date.setDate(date.getDate() + 1);
551
+ this.goToDate(date);
552
+ }
553
+ focusPreviousDay() {
554
+ const date = new Date(this.currentDate);
555
+ date.setDate(date.getDate() - 1);
556
+ this.goToDate(date);
557
+ }
558
+
559
+ // week navigation
560
+ focusNextWeek() {
561
+ const date = new Date(this.currentDate);
562
+ date.setDate(date.getDate() + 7);
563
+ this.goToDate(date);
564
+ }
565
+ focusPreviousWeek() {
566
+ const date = new Date(this.currentDate);
567
+ date.setDate(date.getDate() - 7);
568
+ this.goToDate(date);
569
+ }
570
+ focusFirstDayOfWeek() {
571
+ const date = new Date(this.currentDate);
572
+ const firstDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 0 : 1;
573
+ const dayOfWeek = date.getDay();
574
+ const diff = dayOfWeek >= firstDayOfWeekIndex ? dayOfWeek - firstDayOfWeekIndex : 6 - dayOfWeek;
575
+ date.setDate(date.getDate() - diff);
576
+ date.setHours(0, 0, 0, 0);
577
+ this.goToDate(date);
578
+ }
579
+ focusLastDayOfWeek() {
580
+ const date = new Date(this.currentDate);
581
+ const lastDayOfWeekIndex = this.config.weekStartDay === 'sunday' ? 6 : 0;
582
+ const dayOfWeek = date.getDay();
583
+ const diff = dayOfWeek <= lastDayOfWeekIndex ? lastDayOfWeekIndex - dayOfWeek : 7 - dayOfWeek;
584
+ date.setDate(date.getDate() + diff);
585
+ date.setHours(0, 0, 0, 0);
586
+ this.goToDate(date);
587
+ }
588
+
589
+ // month navigation
590
+ focusNextMonth(event, focus = true) {
591
+ event.preventDefault();
592
+ const date = new Date(this.currentDate);
593
+ date.setMonth(date.getMonth() + 1, 1);
594
+ this.goToDate(date, focus);
595
+ }
596
+ focusPreviousMonth(event, focus = true) {
597
+ event.preventDefault();
598
+ const date = new Date(this.currentDate);
599
+ date.setMonth(date.getMonth() - 1, 1);
600
+ this.goToDate(date, focus);
601
+ }
602
+
603
+ // year navigation
604
+ focusNextYear(event, focus = true) {
605
+ event.preventDefault();
606
+ const date = new Date(this.currentDate);
607
+ date.setFullYear(date.getFullYear() + 1, date.getMonth(), 1);
608
+ this.goToDate(date, focus);
609
+ }
610
+ focusPreviousYear(event, focus = true) {
611
+ event.preventDefault();
612
+ const date = new Date(this.currentDate);
613
+ date.setFullYear(date.getFullYear() - 1, date.getMonth(), 1);
614
+ this.goToDate(date, focus);
615
+ }
616
+
617
+ /**
618
+ * Parse dataset
619
+ *
620
+ * @param {Schema} schema - Component class
621
+ * @param {DOMStringMap} dataset - HTML element dataset
622
+ * @returns {object} Normalised dataset
623
+ */
624
+ parseDataset(schema, dataset) {
625
+ const parsed = {};
626
+ for (const [field,,] of Object.entries(schema.properties)) {
627
+ if (field in dataset) {
628
+ parsed[field] = dataset[field];
629
+ }
630
+ }
631
+ return parsed;
632
+ }
633
+
634
+ /**
635
+ * Config merging function
636
+ *
637
+ * Takes any number of objects and combines them together, with
638
+ * greatest priority on the LAST item passed in.
639
+ *
640
+ * @param {...{ [key: string]: unknown }} configObjects - Config objects to merge
641
+ * @returns {{ [key: string]: unknown }} A merged config object
642
+ */
643
+ mergeConfigs(...configObjects) {
644
+ const formattedConfigObject = {};
645
+
646
+ // Loop through each of the passed objects
647
+ for (const configObject of configObjects) {
648
+ for (const key of Object.keys(configObject)) {
649
+ const option = formattedConfigObject[key];
650
+ const override = configObject[key];
651
+
652
+ // Push their keys one-by-one into formattedConfigObject. Any duplicate
653
+ // keys with object values will be merged, otherwise the new value will
654
+ // override the existing value.
655
+ if (typeof option === 'object' && typeof override === 'object') {
656
+ // @ts-expect-error Index signature for type 'string' is missing
657
+ formattedConfigObject[key] = this.mergeConfigs(option, override);
658
+ } else {
659
+ formattedConfigObject[key] = override;
660
+ }
661
+ }
662
+ }
663
+ return formattedConfigObject;
664
+ }
665
+ }
666
+ class DSCalendarDay {
667
+ /**
668
+ *
669
+ * @param {HTMLElement} button
670
+ * @param {number} index
671
+ * @param {number} row
672
+ * @param {number} column
673
+ * @param {DatePicker} picker
674
+ */
675
+ constructor(button, index, row, column, picker) {
676
+ this.index = index;
677
+ this.row = row;
678
+ this.column = column;
679
+ this.button = button;
680
+ this.picker = picker;
681
+ this.date = new Date();
682
+ this.button.addEventListener('keydown', this.keyPress.bind(this));
683
+ this.button.addEventListener('click', this.click.bind(this));
684
+ }
685
+
686
+ /**
687
+ * @param {Date} day - the Date for the calendar day
688
+ * @param {boolean} hidden - visibility of the day
689
+ * @param {boolean} disabled - is the day selectable or excluded
690
+ */
691
+ update(day, hidden, disabled) {
692
+ const label = day.getDate();
693
+ let accessibleLabel = this.picker.formattedDateHuman(day);
694
+ if (disabled) {
695
+ this.button.setAttribute('aria-disabled', 'true');
696
+ accessibleLabel = `Excluded date, ${accessibleLabel}`;
697
+ } else {
698
+ this.button.removeAttribute('aria-disabled');
699
+ }
700
+ if (hidden) {
701
+ this.button.style.display = 'none';
702
+ } else {
703
+ this.button.style.display = 'block';
704
+ }
705
+ this.button.setAttribute('data-testid', this.picker.formattedDateFromDate(day));
706
+ this.button.innerHTML = `<span class="govuk-visually-hidden">${accessibleLabel}</span><span aria-hidden="true">${label}</span>`;
707
+ this.date = new Date(day);
708
+ }
709
+ click(event) {
710
+ this.picker.goToDate(this.date);
711
+ this.picker.selectDate(this.date);
712
+ event.stopPropagation();
713
+ event.preventDefault();
714
+ }
715
+ keyPress(event) {
716
+ let calendarNavKey = true;
717
+ switch (event.key) {
718
+ case 'ArrowLeft':
719
+ this.picker.focusPreviousDay();
720
+ break;
721
+ case 'ArrowRight':
722
+ this.picker.focusNextDay();
723
+ break;
724
+ case 'ArrowUp':
725
+ this.picker.focusPreviousWeek();
726
+ break;
727
+ case 'ArrowDown':
728
+ this.picker.focusNextWeek();
729
+ break;
730
+ case 'Home':
731
+ this.picker.focusFirstDayOfWeek();
732
+ break;
733
+ case 'End':
734
+ this.picker.focusLastDayOfWeek();
735
+ break;
736
+ case 'PageUp':
737
+ {
738
+ if (event.shiftKey) {
739
+ this.picker.focusPreviousYear(event);
740
+ } else {
741
+ this.picker.focusPreviousMonth(event);
742
+ }
743
+ break;
744
+ }
745
+ case 'PageDown':
746
+ {
747
+ if (event.shiftKey) {
748
+ this.picker.focusNextYear(event);
749
+ } else {
750
+ this.picker.focusNextMonth(event);
751
+ }
752
+ break;
753
+ }
754
+ default:
755
+ calendarNavKey = false;
756
+ break;
757
+ }
758
+ if (calendarNavKey) {
759
+ event.preventDefault();
760
+ event.stopPropagation();
761
+ }
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Date picker config
767
+ *
768
+ * @typedef {object} DatePickerConfig
769
+ * @property {string} [excludedDates] - Dates that cannot be selected
770
+ * @property {string} [excludedDays] - Days that cannot be selected
771
+ * @property {boolean} [leadingZeroes] - Whether to add leading zeroes when populating the field
772
+ * @property {string} [minDate] - The earliest available date
773
+ * @property {string} [maxDate] - The latest available date
774
+ * @property {string} [weekStartDay] - First day of the week in calendar view
775
+ */
776
+
777
+ /**
778
+ * @import { Schema } from '../../all.mjs'
779
+ */
780
+
781
+ exports.DatePicker = DatePicker;
782
+
783
+ }));
784
+ //# sourceMappingURL=date-picker.bundle.js.map