@ministryofjustice/frontend 4.0.0 → 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 (245) 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 +10 -3
  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/page-header-actions/template.njk +1 -1
  107. package/moj/components/pagination/_pagination.scss +2 -2
  108. package/moj/components/pagination/_pagination.scss.map +1 -0
  109. package/moj/components/password-reveal/_password-reveal.scss +2 -0
  110. package/moj/components/password-reveal/_password-reveal.scss.map +1 -0
  111. package/moj/components/password-reveal/password-reveal.bundle.js +49 -0
  112. package/moj/components/password-reveal/password-reveal.bundle.js.map +1 -0
  113. package/moj/components/password-reveal/password-reveal.bundle.mjs +41 -0
  114. package/moj/components/password-reveal/password-reveal.bundle.mjs.map +1 -0
  115. package/moj/components/password-reveal/password-reveal.mjs +36 -31
  116. package/moj/components/password-reveal/password-reveal.mjs.map +1 -1
  117. package/moj/components/primary-navigation/_primary-navigation.scss +2 -0
  118. package/moj/components/primary-navigation/_primary-navigation.scss.map +1 -0
  119. package/moj/components/progress-bar/_progress-bar.scss +2 -0
  120. package/moj/components/progress-bar/_progress-bar.scss.map +1 -0
  121. package/moj/components/rich-text-editor/README.md +15 -9
  122. package/moj/components/rich-text-editor/_rich-text-editor.scss +2 -0
  123. package/moj/components/rich-text-editor/_rich-text-editor.scss.map +1 -0
  124. package/moj/components/rich-text-editor/rich-text-editor.bundle.js +145 -0
  125. package/moj/components/rich-text-editor/rich-text-editor.bundle.js.map +1 -0
  126. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs +137 -0
  127. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs.map +1 -0
  128. package/moj/components/rich-text-editor/rich-text-editor.mjs +124 -145
  129. package/moj/components/rich-text-editor/rich-text-editor.mjs.map +1 -1
  130. package/moj/components/search/_search.scss +2 -0
  131. package/moj/components/search/_search.scss.map +1 -0
  132. package/moj/components/search-toggle/{search-toggle.scss → _search-toggle.scss} +2 -0
  133. package/moj/components/search-toggle/_search-toggle.scss.map +1 -0
  134. package/moj/components/search-toggle/search-toggle.bundle.js +54 -0
  135. package/moj/components/search-toggle/search-toggle.bundle.js.map +1 -0
  136. package/moj/components/search-toggle/search-toggle.bundle.mjs +46 -0
  137. package/moj/components/search-toggle/search-toggle.bundle.mjs.map +1 -0
  138. package/moj/components/search-toggle/search-toggle.mjs +40 -49
  139. package/moj/components/search-toggle/search-toggle.mjs.map +1 -1
  140. package/moj/components/side-navigation/_side-navigation.scss +2 -0
  141. package/moj/components/side-navigation/_side-navigation.scss.map +1 -0
  142. package/moj/components/sortable-table/_sortable-table.scss +2 -2
  143. package/moj/components/sortable-table/_sortable-table.scss.map +1 -0
  144. package/moj/components/sortable-table/sortable-table.bundle.js +134 -0
  145. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -0
  146. package/moj/components/sortable-table/sortable-table.bundle.mjs +126 -0
  147. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -0
  148. package/moj/components/sortable-table/sortable-table.mjs +117 -130
  149. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  150. package/moj/components/sub-navigation/_sub-navigation.scss +2 -0
  151. package/moj/components/sub-navigation/_sub-navigation.scss.map +1 -0
  152. package/moj/components/tag/_tag.scss +2 -0
  153. package/moj/components/tag/_tag.scss.map +1 -0
  154. package/moj/components/task-list/_task-list.scss +2 -0
  155. package/moj/components/task-list/_task-list.scss.map +1 -0
  156. package/moj/components/ticket-panel/_ticket-panel.scss +2 -0
  157. package/moj/components/ticket-panel/_ticket-panel.scss.map +1 -0
  158. package/moj/components/timeline/_timeline.scss +2 -0
  159. package/moj/components/timeline/_timeline.scss.map +1 -0
  160. package/moj/filters/all.js +44 -22
  161. package/moj/helpers/_all.scss +2 -0
  162. package/moj/helpers/_all.scss.map +1 -0
  163. package/moj/helpers/_hidden.scss +2 -0
  164. package/moj/helpers/_hidden.scss.map +1 -0
  165. package/moj/helpers/_links.scss +2 -0
  166. package/moj/helpers/_links.scss.map +1 -0
  167. package/moj/{helpers.js → helpers.bundle.js} +37 -42
  168. package/moj/helpers.bundle.js.map +1 -0
  169. package/moj/helpers.bundle.mjs +179 -0
  170. package/moj/helpers.bundle.mjs.map +1 -0
  171. package/moj/helpers.mjs +52 -28
  172. package/moj/helpers.mjs.map +1 -1
  173. package/moj/init.js +11 -2
  174. package/moj/moj-frontend.min.css +1 -1
  175. package/moj/moj-frontend.min.css.map +1 -1
  176. package/moj/moj-frontend.min.js +1 -1
  177. package/moj/moj-frontend.min.js.map +1 -1
  178. package/moj/objects/_all.scss +2 -0
  179. package/moj/objects/_all.scss.map +1 -0
  180. package/moj/objects/_button-group.scss +17 -1
  181. package/moj/objects/_button-group.scss.map +1 -0
  182. package/moj/objects/_filter-layout.scss +2 -0
  183. package/moj/objects/_filter-layout.scss.map +1 -0
  184. package/moj/objects/_scrollable-pane.scss +2 -0
  185. package/moj/objects/_scrollable-pane.scss.map +1 -0
  186. package/moj/objects/_width-container.scss +2 -0
  187. package/moj/objects/_width-container.scss.map +1 -0
  188. package/moj/settings/_all.scss +2 -0
  189. package/moj/settings/_all.scss.map +1 -0
  190. package/moj/settings/_assets.scss +2 -0
  191. package/moj/settings/_assets.scss.map +1 -0
  192. package/moj/settings/_colours.scss +2 -0
  193. package/moj/settings/_colours.scss.map +1 -0
  194. package/moj/settings/_measurements.scss +2 -0
  195. package/moj/settings/_measurements.scss.map +1 -0
  196. package/moj/settings/_typography.scss +2 -0
  197. package/moj/settings/_typography.scss.map +1 -0
  198. package/moj/template.njk +13 -0
  199. package/moj/utilities/_all.scss +2 -0
  200. package/moj/utilities/_all.scss.map +1 -0
  201. package/moj/utilities/_hidden.scss +2 -0
  202. package/moj/utilities/_hidden.scss.map +1 -0
  203. package/moj/utilities/_width-container.scss +2 -0
  204. package/moj/utilities/_width-container.scss.map +1 -0
  205. package/moj/vendor/govuk-frontend/_base.scss +2 -0
  206. package/moj/vendor/govuk-frontend/_base.scss.map +1 -0
  207. package/moj/vendor/govuk-frontend/_index.scss +2 -0
  208. package/moj/vendor/govuk-frontend/_index.scss.map +1 -0
  209. package/moj/{version.js → version.bundle.js} +1 -1
  210. package/moj/version.bundle.js.map +1 -0
  211. package/moj/version.bundle.mjs +4 -0
  212. package/moj/version.bundle.mjs.map +1 -0
  213. package/moj/version.mjs.map +1 -1
  214. package/package.json +5 -6
  215. package/moj/all.jquery.min.js +0 -1
  216. package/moj/all.jquery.min.js.map +0 -1
  217. package/moj/all.js +0 -2662
  218. package/moj/all.js.map +0 -1
  219. package/moj/components/add-another/add-another.js +0 -115
  220. package/moj/components/add-another/add-another.js.map +0 -1
  221. package/moj/components/alert/alert.js +0 -356
  222. package/moj/components/alert/alert.js.map +0 -1
  223. package/moj/components/alert/alert.spec.helper.js.map +0 -1
  224. package/moj/components/button-menu/button-menu.js.map +0 -1
  225. package/moj/components/date-picker/date-picker.js.map +0 -1
  226. package/moj/components/filter-toggle-button/filter-toggle-button.js +0 -102
  227. package/moj/components/filter-toggle-button/filter-toggle-button.js.map +0 -1
  228. package/moj/components/form-validator/form-validator.js +0 -205
  229. package/moj/components/form-validator/form-validator.js.map +0 -1
  230. package/moj/components/multi-file-upload/multi-file-upload.js +0 -241
  231. package/moj/components/multi-file-upload/multi-file-upload.js.map +0 -1
  232. package/moj/components/multi-select/multi-select.js +0 -86
  233. package/moj/components/multi-select/multi-select.js.map +0 -1
  234. package/moj/components/password-reveal/password-reveal.js +0 -44
  235. package/moj/components/password-reveal/password-reveal.js.map +0 -1
  236. package/moj/components/rich-text-editor/rich-text-editor.js +0 -166
  237. package/moj/components/rich-text-editor/rich-text-editor.js.map +0 -1
  238. package/moj/components/search-toggle/search-toggle.js +0 -63
  239. package/moj/components/search-toggle/search-toggle.js.map +0 -1
  240. package/moj/components/sortable-table/sortable-table.js +0 -147
  241. package/moj/components/sortable-table/sortable-table.js.map +0 -1
  242. package/moj/helpers.js.map +0 -1
  243. package/moj/vendor/html5shiv.js +0 -326
  244. package/moj/vendor/jquery.js +0 -9300
  245. package/moj/version.js.map +0 -1
@@ -1,94 +1,88 @@
1
- function FilterToggleButton(options) {
2
- this.options = options;
3
- this.container = $(this.options.toggleButton.container);
4
- this.filterContainer = $(this.options.filter.container);
5
-
6
- this.createToggleButton();
7
- this.setupResponsiveChecks();
8
- this.filterContainer.attr('tabindex', '-1');
9
- if (this.options.startHidden) {
10
- this.hideMenu();
1
+ class FilterToggleButton {
2
+ constructor(options) {
3
+ this.options = options;
4
+ this.container = this.options.toggleButton.container;
5
+ this.filterContainer = this.options.filter.container;
6
+ this.createToggleButton();
7
+ this.setupResponsiveChecks();
8
+ this.filterContainer.setAttribute('tabindex', '-1');
9
+ if (this.options.startHidden) {
10
+ this.hideMenu();
11
+ }
11
12
  }
12
- }
13
-
14
- FilterToggleButton.prototype.setupResponsiveChecks = function () {
15
- this.mq = window.matchMedia(this.options.bigModeMediaQuery);
16
- this.mq.addListener($.proxy(this, 'checkMode'));
17
- this.checkMode(this.mq);
18
- };
19
-
20
- FilterToggleButton.prototype.createToggleButton = function () {
21
- this.menuButton = $(
22
- `<button class="govuk-button ${this.options.toggleButton.classes}" type="button" aria-haspopup="true" aria-expanded="false">${this.options.toggleButton.showText}</button>`
23
- );
24
- this.menuButton.on('click', $.proxy(this, 'onMenuButtonClick'));
25
- this.container.append(this.menuButton);
26
- };
27
-
28
- FilterToggleButton.prototype.checkMode = function (mq) {
29
- if (mq.matches) {
30
- this.enableBigMode();
31
- } else {
32
- this.enableSmallMode();
13
+ setupResponsiveChecks() {
14
+ this.mq = window.matchMedia(this.options.bigModeMediaQuery);
15
+ this.mq.addListener(this.checkMode.bind(this));
16
+ this.checkMode(this.mq);
33
17
  }
34
- };
35
-
36
- FilterToggleButton.prototype.enableBigMode = function () {
37
- this.showMenu();
38
- this.removeCloseButton();
39
- };
40
-
41
- FilterToggleButton.prototype.enableSmallMode = function () {
42
- this.hideMenu();
43
- this.addCloseButton();
44
- };
45
-
46
- FilterToggleButton.prototype.addCloseButton = function () {
47
- if (this.options.closeButton) {
48
- this.closeButton = $(
49
- `<button class="moj-filter__close" type="button">${this.options.closeButton.text}</button>`
50
- );
51
- this.closeButton.on('click', $.proxy(this, 'onCloseClick'));
52
- $(this.options.closeButton.container).append(this.closeButton);
18
+ createToggleButton() {
19
+ this.menuButton = document.createElement('button');
20
+ this.menuButton.setAttribute('type', 'button');
21
+ this.menuButton.setAttribute('aria-haspopup', 'true');
22
+ this.menuButton.setAttribute('aria-expanded', 'false');
23
+ this.menuButton.className = `govuk-button ${this.options.toggleButton.classes}`;
24
+ this.menuButton.textContent = this.options.toggleButton.showText;
25
+ this.menuButton.addEventListener('click', this.onMenuButtonClick.bind(this));
26
+ this.container.append(this.menuButton);
53
27
  }
54
- };
55
-
56
- FilterToggleButton.prototype.onCloseClick = function () {
57
- this.hideMenu();
58
- this.menuButton.focus();
59
- };
60
-
61
- FilterToggleButton.prototype.removeCloseButton = function () {
62
- if (this.closeButton) {
63
- this.closeButton.remove();
64
- this.closeButton = null;
28
+ checkMode(mq) {
29
+ if (mq.matches) {
30
+ this.enableBigMode();
31
+ } else {
32
+ this.enableSmallMode();
33
+ }
65
34
  }
66
- };
67
-
68
- FilterToggleButton.prototype.hideMenu = function () {
69
- this.menuButton.attr('aria-expanded', 'false');
70
- this.filterContainer.addClass('moj-js-hidden');
71
- this.menuButton.text(this.options.toggleButton.showText);
72
- };
73
-
74
- FilterToggleButton.prototype.showMenu = function () {
75
- this.menuButton.attr('aria-expanded', 'true');
76
- this.filterContainer.removeClass('moj-js-hidden');
77
- this.menuButton.text(this.options.toggleButton.hideText);
78
- };
79
-
80
- FilterToggleButton.prototype.onMenuButtonClick = function () {
81
- this.toggle();
82
- };
83
-
84
- FilterToggleButton.prototype.toggle = function () {
85
- if (this.menuButton.attr('aria-expanded') === 'false') {
35
+ enableBigMode() {
86
36
  this.showMenu();
87
- this.filterContainer.get(0).focus();
88
- } else {
37
+ this.removeCloseButton();
38
+ }
39
+ enableSmallMode() {
40
+ this.hideMenu();
41
+ this.addCloseButton();
42
+ }
43
+ addCloseButton() {
44
+ if (!this.options.closeButton) {
45
+ return;
46
+ }
47
+ this.closeButton = document.createElement('button');
48
+ this.closeButton.setAttribute('type', 'button');
49
+ this.closeButton.className = 'moj-filter__close';
50
+ this.closeButton.textContent = this.options.closeButton.text;
51
+ this.closeButton.addEventListener('click', this.onCloseClick.bind(this));
52
+ this.options.closeButton.container.append(this.closeButton);
53
+ }
54
+ onCloseClick() {
89
55
  this.hideMenu();
56
+ this.menuButton.focus();
90
57
  }
91
- };
58
+ removeCloseButton() {
59
+ if (this.closeButton) {
60
+ this.closeButton.remove();
61
+ this.closeButton = null;
62
+ }
63
+ }
64
+ hideMenu() {
65
+ this.menuButton.setAttribute('aria-expanded', 'false');
66
+ this.filterContainer.classList.add('moj-js-hidden');
67
+ this.menuButton.textContent = this.options.toggleButton.showText;
68
+ }
69
+ showMenu() {
70
+ this.menuButton.setAttribute('aria-expanded', 'true');
71
+ this.filterContainer.classList.remove('moj-js-hidden');
72
+ this.menuButton.textContent = this.options.toggleButton.hideText;
73
+ }
74
+ onMenuButtonClick() {
75
+ this.toggle();
76
+ }
77
+ toggle() {
78
+ if (this.menuButton.getAttribute('aria-expanded') === 'false') {
79
+ this.showMenu();
80
+ this.filterContainer.focus();
81
+ } else {
82
+ this.hideMenu();
83
+ }
84
+ }
85
+ }
92
86
 
93
87
  export { FilterToggleButton };
94
88
  //# sourceMappingURL=filter-toggle-button.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter-toggle-button.mjs","sources":["../../../../src/moj/components/filter-toggle-button/filter-toggle-button.mjs"],"sourcesContent":["import $ from 'jquery'\n\nexport function FilterToggleButton(options) {\n this.options = options\n this.container = $(this.options.toggleButton.container)\n this.filterContainer = $(this.options.filter.container)\n\n this.createToggleButton()\n this.setupResponsiveChecks()\n this.filterContainer.attr('tabindex', '-1')\n if (this.options.startHidden) {\n this.hideMenu()\n }\n}\n\nFilterToggleButton.prototype.setupResponsiveChecks = function () {\n this.mq = window.matchMedia(this.options.bigModeMediaQuery)\n this.mq.addListener($.proxy(this, 'checkMode'))\n this.checkMode(this.mq)\n}\n\nFilterToggleButton.prototype.createToggleButton = function () {\n this.menuButton = $(\n `<button class=\"govuk-button ${this.options.toggleButton.classes}\" type=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">${this.options.toggleButton.showText}</button>`\n )\n this.menuButton.on('click', $.proxy(this, 'onMenuButtonClick'))\n this.container.append(this.menuButton)\n}\n\nFilterToggleButton.prototype.checkMode = function (mq) {\n if (mq.matches) {\n this.enableBigMode()\n } else {\n this.enableSmallMode()\n }\n}\n\nFilterToggleButton.prototype.enableBigMode = function () {\n this.showMenu()\n this.removeCloseButton()\n}\n\nFilterToggleButton.prototype.enableSmallMode = function () {\n this.hideMenu()\n this.addCloseButton()\n}\n\nFilterToggleButton.prototype.addCloseButton = function () {\n if (this.options.closeButton) {\n this.closeButton = $(\n `<button class=\"moj-filter__close\" type=\"button\">${this.options.closeButton.text}</button>`\n )\n this.closeButton.on('click', $.proxy(this, 'onCloseClick'))\n $(this.options.closeButton.container).append(this.closeButton)\n }\n}\n\nFilterToggleButton.prototype.onCloseClick = function () {\n this.hideMenu()\n this.menuButton.focus()\n}\n\nFilterToggleButton.prototype.removeCloseButton = function () {\n if (this.closeButton) {\n this.closeButton.remove()\n this.closeButton = null\n }\n}\n\nFilterToggleButton.prototype.hideMenu = function () {\n this.menuButton.attr('aria-expanded', 'false')\n this.filterContainer.addClass('moj-js-hidden')\n this.menuButton.text(this.options.toggleButton.showText)\n}\n\nFilterToggleButton.prototype.showMenu = function () {\n this.menuButton.attr('aria-expanded', 'true')\n this.filterContainer.removeClass('moj-js-hidden')\n this.menuButton.text(this.options.toggleButton.hideText)\n}\n\nFilterToggleButton.prototype.onMenuButtonClick = function () {\n this.toggle()\n}\n\nFilterToggleButton.prototype.toggle = function () {\n if (this.menuButton.attr('aria-expanded') === 'false') {\n this.showMenu()\n this.filterContainer.get(0).focus()\n } else {\n this.hideMenu()\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filter-toggle-button.mjs","sources":["../../../../src/moj/components/filter-toggle-button/filter-toggle-button.mjs"],"sourcesContent":["export class FilterToggleButton {\n constructor(options) {\n this.options = options\n this.container = this.options.toggleButton.container\n this.filterContainer = this.options.filter.container\n\n this.createToggleButton()\n this.setupResponsiveChecks()\n this.filterContainer.setAttribute('tabindex', '-1')\n if (this.options.startHidden) {\n this.hideMenu()\n }\n }\n\n setupResponsiveChecks() {\n this.mq = window.matchMedia(this.options.bigModeMediaQuery)\n this.mq.addListener(this.checkMode.bind(this))\n this.checkMode(this.mq)\n }\n\n createToggleButton() {\n this.menuButton = document.createElement('button')\n this.menuButton.setAttribute('type', 'button')\n this.menuButton.setAttribute('aria-haspopup', 'true')\n this.menuButton.setAttribute('aria-expanded', 'false')\n\n this.menuButton.className = `govuk-button ${this.options.toggleButton.classes}`\n this.menuButton.textContent = this.options.toggleButton.showText\n\n this.menuButton.addEventListener('click', this.onMenuButtonClick.bind(this))\n this.container.append(this.menuButton)\n }\n\n checkMode(mq) {\n if (mq.matches) {\n this.enableBigMode()\n } else {\n this.enableSmallMode()\n }\n }\n\n enableBigMode() {\n this.showMenu()\n this.removeCloseButton()\n }\n\n enableSmallMode() {\n this.hideMenu()\n this.addCloseButton()\n }\n\n addCloseButton() {\n if (!this.options.closeButton) {\n return\n }\n\n this.closeButton = document.createElement('button')\n this.closeButton.setAttribute('type', 'button')\n\n this.closeButton.className = 'moj-filter__close'\n this.closeButton.textContent = this.options.closeButton.text\n\n this.closeButton.addEventListener('click', this.onCloseClick.bind(this))\n this.options.closeButton.container.append(this.closeButton)\n }\n\n onCloseClick() {\n this.hideMenu()\n this.menuButton.focus()\n }\n\n removeCloseButton() {\n if (this.closeButton) {\n this.closeButton.remove()\n this.closeButton = null\n }\n }\n\n hideMenu() {\n this.menuButton.setAttribute('aria-expanded', 'false')\n this.filterContainer.classList.add('moj-js-hidden')\n this.menuButton.textContent = this.options.toggleButton.showText\n }\n\n showMenu() {\n this.menuButton.setAttribute('aria-expanded', 'true')\n this.filterContainer.classList.remove('moj-js-hidden')\n this.menuButton.textContent = this.options.toggleButton.hideText\n }\n\n onMenuButtonClick() {\n this.toggle()\n }\n\n toggle() {\n if (this.menuButton.getAttribute('aria-expanded') === 'false') {\n this.showMenu()\n this.filterContainer.focus()\n } else {\n this.hideMenu()\n }\n }\n}\n"],"names":["FilterToggleButton","constructor","options","container","toggleButton","filterContainer","filter","createToggleButton","setupResponsiveChecks","setAttribute","startHidden","hideMenu","mq","window","matchMedia","bigModeMediaQuery","addListener","checkMode","bind","menuButton","document","createElement","className","classes","textContent","showText","addEventListener","onMenuButtonClick","append","matches","enableBigMode","enableSmallMode","showMenu","removeCloseButton","addCloseButton","closeButton","text","onCloseClick","focus","remove","classList","add","hideText","toggle","getAttribute"],"mappings":"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"}
@@ -0,0 +1,198 @@
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
+ function removeAttributeValue(el, attr, value) {
8
+ let re, m;
9
+ if (el.getAttribute(attr)) {
10
+ if (el.getAttribute(attr) === value) {
11
+ el.removeAttribute(attr);
12
+ } else {
13
+ re = new RegExp(`(^|\\s)${value}(\\s|$)`);
14
+ m = el.getAttribute(attr).match(re);
15
+ if (m && m.length === 3) {
16
+ el.setAttribute(attr, el.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : ''));
17
+ }
18
+ }
19
+ }
20
+ }
21
+ function addAttributeValue(el, attr, value) {
22
+ let re;
23
+ if (!el.getAttribute(attr)) {
24
+ el.setAttribute(attr, value);
25
+ } else {
26
+ re = new RegExp(`(^|\\s)${value}(\\s|$)`);
27
+ if (!re.test(el.getAttribute(attr))) {
28
+ el.setAttribute(attr, `${el.getAttribute(attr)} ${value}`);
29
+ }
30
+ }
31
+ }
32
+
33
+ class FormValidator {
34
+ /**
35
+ * @param {Element | null} form - HTML element to use for form validator
36
+ * @param {FormValidatorConfig} [config] - Button menu config
37
+ */
38
+ constructor(form, config = {}) {
39
+ if (!form || !(form instanceof HTMLFormElement)) {
40
+ return this;
41
+ }
42
+ this.form = form;
43
+ this.errors = [];
44
+ this.validators = [];
45
+ this.form.addEventListener('submit', this.onSubmit.bind(this));
46
+ this.summary = config.summary || document.querySelector('.govuk-error-summary');
47
+ this.originalTitle = document.title;
48
+ }
49
+ escapeHtml(string) {
50
+ return String(string).replace(/[&<>"'`=/]/g, function fromEntityMap(s) {
51
+ return FormValidator.entityMap[s];
52
+ });
53
+ }
54
+ resetTitle() {
55
+ document.title = this.originalTitle;
56
+ }
57
+ updateTitle() {
58
+ document.title = `${this.errors.length} errors - ${document.title}`;
59
+ }
60
+ showSummary() {
61
+ this.summary.innerHTML = this.getSummaryHtml();
62
+ this.summary.classList.remove('moj-hidden');
63
+ this.summary.setAttribute('aria-labelledby', 'errorSummary-heading');
64
+ this.summary.focus();
65
+ }
66
+ getSummaryHtml() {
67
+ let html = '<h2 id="error-summary-title" class="govuk-error-summary__title">There is a problem</h2>';
68
+ html += '<div class="govuk-error-summary__body">';
69
+ html += '<ul class="govuk-list govuk-error-summary__list">';
70
+ for (const error of this.errors) {
71
+ html += '<li>';
72
+ html += `<a href="#${this.escapeHtml(error.fieldName)}">`;
73
+ html += this.escapeHtml(error.message);
74
+ html += '</a>';
75
+ html += '</li>';
76
+ }
77
+ html += '</ul>';
78
+ html += '</div>';
79
+ return html;
80
+ }
81
+ hideSummary() {
82
+ this.summary.classList.add('moj-hidden');
83
+ this.summary.removeAttribute('aria-labelledby');
84
+ }
85
+ onSubmit(event) {
86
+ this.removeInlineErrors();
87
+ this.hideSummary();
88
+ this.resetTitle();
89
+ if (!this.validate()) {
90
+ event.preventDefault();
91
+ this.updateTitle();
92
+ this.showSummary();
93
+ this.showInlineErrors();
94
+ }
95
+ }
96
+ showInlineErrors() {
97
+ for (const error of this.errors) {
98
+ this.showInlineError(error);
99
+ }
100
+ }
101
+ showInlineError(error) {
102
+ const errorSpan = document.createElement('span');
103
+ errorSpan.id = `${error.fieldName}-error`;
104
+ errorSpan.classList.add('govuk-error-message');
105
+ errorSpan.innerHTML = this.escapeHtml(error.message);
106
+ const control = document.querySelector(`#${error.fieldName}`);
107
+ const fieldset = control.closest('.govuk-fieldset');
108
+ const fieldContainer = (fieldset || control).closest('.govuk-form-group');
109
+ const label = fieldContainer.querySelector('label');
110
+ const legend = fieldContainer.querySelector('legend');
111
+ fieldContainer.classList.add('govuk-form-group--error');
112
+ if (fieldset && legend) {
113
+ legend.after(errorSpan);
114
+ fieldContainer.setAttribute('aria-invalid', 'true');
115
+ addAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
116
+ } else if (label && control) {
117
+ label.after(errorSpan);
118
+ control.setAttribute('aria-invalid', 'true');
119
+ addAttributeValue(control, 'aria-describedby', errorSpan.id);
120
+ }
121
+ }
122
+ removeInlineErrors() {
123
+ for (const error of this.errors) {
124
+ this.removeInlineError(error);
125
+ }
126
+ }
127
+ removeInlineError(error) {
128
+ const errorSpan = document.querySelector(`#${error.fieldName}-error`);
129
+ const control = document.querySelector(`#${error.fieldName}`);
130
+ const fieldset = control.closest('.govuk-fieldset');
131
+ const fieldContainer = (fieldset || control).closest('.govuk-form-group');
132
+ const label = fieldContainer.querySelector('label');
133
+ const legend = fieldContainer.querySelector('legend');
134
+ errorSpan.remove();
135
+ fieldContainer.classList.remove('govuk-form-group--error');
136
+ if (fieldset && legend) {
137
+ fieldContainer.removeAttribute('aria-invalid');
138
+ removeAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
139
+ } else if (label && control) {
140
+ control.removeAttribute('aria-invalid');
141
+ removeAttributeValue(control, 'aria-describedby', errorSpan.id);
142
+ }
143
+ }
144
+ addValidator(fieldName, rules) {
145
+ this.validators.push({
146
+ fieldName,
147
+ rules,
148
+ field: this.form.elements[fieldName]
149
+ });
150
+ }
151
+ validate() {
152
+ this.errors = [];
153
+ let validator = null;
154
+ let validatorReturnValue = true;
155
+ let i;
156
+ let j;
157
+ for (i = 0; i < this.validators.length; i++) {
158
+ validator = this.validators[i];
159
+ for (j = 0; j < validator.rules.length; j++) {
160
+ validatorReturnValue = validator.rules[j].method(validator.field, validator.rules[j].params);
161
+ if (typeof validatorReturnValue === 'boolean' && !validatorReturnValue) {
162
+ this.errors.push({
163
+ fieldName: validator.fieldName,
164
+ message: validator.rules[j].message
165
+ });
166
+ break;
167
+ } else if (typeof validatorReturnValue === 'string') {
168
+ this.errors.push({
169
+ fieldName: validatorReturnValue,
170
+ message: validator.rules[j].message
171
+ });
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ return this.errors.length === 0;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * @typedef {object} FormValidatorConfig
182
+ * @property {HTMLElement} [summary] - HTML element to use for error summary
183
+ */
184
+ FormValidator.entityMap = {
185
+ '&': '&amp;',
186
+ '<': '&lt;',
187
+ '>': '&gt;',
188
+ '"': '&quot;',
189
+ "'": '&#39;',
190
+ '/': '&#x2F;',
191
+ '`': '&#x60;',
192
+ '=': '&#x3D;'
193
+ };
194
+
195
+ exports.FormValidator = FormValidator;
196
+
197
+ }));
198
+ //# sourceMappingURL=form-validator.bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-validator.bundle.js","sources":["../../../../src/moj/helpers.mjs","../../../../src/moj/components/form-validator/form-validator.mjs"],"sourcesContent":["export function removeAttributeValue(el, attr, value) {\n let re, m\n if (el.getAttribute(attr)) {\n if (el.getAttribute(attr) === value) {\n el.removeAttribute(attr)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n m = el.getAttribute(attr).match(re)\n if (m && m.length === 3) {\n el.setAttribute(\n attr,\n el.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : '')\n )\n }\n }\n }\n}\n\nexport function addAttributeValue(el, attr, value) {\n let re\n if (!el.getAttribute(attr)) {\n el.setAttribute(attr, value)\n } else {\n re = new RegExp(`(^|\\\\s)${value}(\\\\s|$)`)\n if (!re.test(el.getAttribute(attr))) {\n el.setAttribute(attr, `${el.getAttribute(attr)} ${value}`)\n }\n }\n}\n\nexport function dragAndDropSupported() {\n const div = document.createElement('div')\n return typeof div.ondrop !== 'undefined'\n}\n\nexport function formDataSupported() {\n return typeof FormData === 'function'\n}\n\nexport function fileApiSupported() {\n const input = document.createElement('input')\n input.type = 'file'\n return typeof input.files !== 'undefined'\n}\n\n/**\n * Find an elements next sibling\n *\n * Utility function to find an elements next sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getNextSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the next sibling element\n let $sibling = $element.nextElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.nextElementSibling\n }\n}\n\n/**\n * Find an elements preceding sibling\n *\n * Utility function to find an elements previous sibling matching the provided\n * selector.\n *\n * @param {Element | null} $element - Element to find siblings for\n * @param {string} [selector] - selector for required sibling\n */\nexport function getPreviousSibling($element, selector) {\n if (!$element || !($element instanceof HTMLElement)) {\n return\n }\n\n // Get the previous sibling element\n let $sibling = $element.previousElementSibling\n\n // If there's no selector, return the first sibling\n if (!selector) return $sibling\n\n // If the sibling matches our selector, use it\n // If not, jump to the next sibling and continue the loop\n while ($sibling) {\n if ($sibling.matches(selector)) return $sibling\n $sibling = $sibling.previousElementSibling\n }\n}\n\n/**\n * @param {Element | null} $element\n * @param {string} [selector]\n */\nexport function findNearestMatchingElement($element, selector) {\n // If no element or selector is provided, return\n if (!$element || !($element instanceof HTMLElement) || !selector) {\n return\n }\n\n // Start with the current element\n let $currentElement = $element\n\n while ($currentElement) {\n // First check the current element\n if ($currentElement.matches(selector)) {\n return $currentElement\n }\n\n // Check all previous siblings\n let $sibling = $currentElement.previousElementSibling\n while ($sibling) {\n // Check if the sibling itself is a heading\n if ($sibling.matches(selector)) {\n return $sibling\n }\n $sibling = $sibling.previousElementSibling\n }\n\n // If no match found in siblings, move up to parent\n $currentElement = $currentElement.parentElement\n }\n}\n\n/**\n * Move focus to element\n *\n * Sets tabindex to -1 to make the element programmatically focusable,\n * but removes it on blur as the element doesn't need to be focused again.\n *\n * @param {HTMLElement} $element - HTML element\n * @param {object} [options] - Handler options\n * @param {function(this: HTMLElement): void} [options.onBeforeFocus] - Callback before focus\n * @param {function(this: HTMLElement): void} [options.onBlur] - Callback on blur\n */\nexport function setFocus($element, options = {}) {\n const isFocusable = $element.getAttribute('tabindex')\n\n if (!isFocusable) {\n $element.setAttribute('tabindex', '-1')\n }\n\n /**\n * Handle element focus\n */\n function onFocus() {\n $element.addEventListener('blur', onBlur, { once: true })\n }\n\n /**\n * Handle element blur\n */\n function onBlur() {\n if (options.onBlur) {\n options.onBlur.call($element)\n }\n\n if (!isFocusable) {\n $element.removeAttribute('tabindex')\n }\n }\n\n // Add listener to reset element on blur, after focus\n $element.addEventListener('focus', onFocus, { once: true })\n\n // Focus element\n if (options.onBeforeFocus) {\n options.onBeforeFocus.call($element)\n }\n $element.focus()\n}\n","import { addAttributeValue, removeAttributeValue } from '../../helpers.mjs'\n\nexport class FormValidator {\n /**\n * @param {Element | null} form - HTML element to use for form validator\n * @param {FormValidatorConfig} [config] - Button menu config\n */\n constructor(form, config = {}) {\n if (!form || !(form instanceof HTMLFormElement)) {\n return this\n }\n\n this.form = form\n this.errors = []\n this.validators = []\n this.form.addEventListener('submit', this.onSubmit.bind(this))\n this.summary =\n config.summary || document.querySelector('.govuk-error-summary')\n this.originalTitle = document.title\n }\n\n escapeHtml(string) {\n return String(string).replace(/[&<>\"'`=/]/g, function fromEntityMap(s) {\n return FormValidator.entityMap[s]\n })\n }\n\n resetTitle() {\n document.title = this.originalTitle\n }\n\n updateTitle() {\n document.title = `${this.errors.length} errors - ${document.title}`\n }\n\n showSummary() {\n this.summary.innerHTML = this.getSummaryHtml()\n this.summary.classList.remove('moj-hidden')\n this.summary.setAttribute('aria-labelledby', 'errorSummary-heading')\n this.summary.focus()\n }\n\n getSummaryHtml() {\n let html =\n '<h2 id=\"error-summary-title\" class=\"govuk-error-summary__title\">There is a problem</h2>'\n html += '<div class=\"govuk-error-summary__body\">'\n html += '<ul class=\"govuk-list govuk-error-summary__list\">'\n for (const error of this.errors) {\n html += '<li>'\n html += `<a href=\"#${this.escapeHtml(error.fieldName)}\">`\n html += this.escapeHtml(error.message)\n html += '</a>'\n html += '</li>'\n }\n html += '</ul>'\n html += '</div>'\n return html\n }\n\n hideSummary() {\n this.summary.classList.add('moj-hidden')\n this.summary.removeAttribute('aria-labelledby')\n }\n\n onSubmit(event) {\n this.removeInlineErrors()\n this.hideSummary()\n this.resetTitle()\n if (!this.validate()) {\n event.preventDefault()\n this.updateTitle()\n this.showSummary()\n this.showInlineErrors()\n }\n }\n\n showInlineErrors() {\n for (const error of this.errors) {\n this.showInlineError(error)\n }\n }\n\n showInlineError(error) {\n const errorSpan = document.createElement('span')\n errorSpan.id = `${error.fieldName}-error`\n errorSpan.classList.add('govuk-error-message')\n errorSpan.innerHTML = this.escapeHtml(error.message)\n\n const control = document.querySelector(`#${error.fieldName}`)\n const fieldset = control.closest('.govuk-fieldset')\n const fieldContainer = (fieldset || control).closest('.govuk-form-group')\n\n const label = fieldContainer.querySelector('label')\n const legend = fieldContainer.querySelector('legend')\n\n fieldContainer.classList.add('govuk-form-group--error')\n\n if (fieldset && legend) {\n legend.after(errorSpan)\n fieldContainer.setAttribute('aria-invalid', 'true')\n addAttributeValue(fieldset, 'aria-describedby', errorSpan.id)\n } else if (label && control) {\n label.after(errorSpan)\n control.setAttribute('aria-invalid', 'true')\n addAttributeValue(control, 'aria-describedby', errorSpan.id)\n }\n }\n\n removeInlineErrors() {\n for (const error of this.errors) {\n this.removeInlineError(error)\n }\n }\n\n removeInlineError(error) {\n const errorSpan = document.querySelector(`#${error.fieldName}-error`)\n\n const control = document.querySelector(`#${error.fieldName}`)\n const fieldset = control.closest('.govuk-fieldset')\n const fieldContainer = (fieldset || control).closest('.govuk-form-group')\n\n const label = fieldContainer.querySelector('label')\n const legend = fieldContainer.querySelector('legend')\n\n errorSpan.remove()\n fieldContainer.classList.remove('govuk-form-group--error')\n\n if (fieldset && legend) {\n fieldContainer.removeAttribute('aria-invalid')\n removeAttributeValue(fieldset, 'aria-describedby', errorSpan.id)\n } else if (label && control) {\n control.removeAttribute('aria-invalid')\n removeAttributeValue(control, 'aria-describedby', errorSpan.id)\n }\n }\n\n addValidator(fieldName, rules) {\n this.validators.push({\n fieldName,\n rules,\n field: this.form.elements[fieldName]\n })\n }\n\n validate() {\n this.errors = []\n let validator = null\n let validatorReturnValue = true\n let i\n let j\n for (i = 0; i < this.validators.length; i++) {\n validator = this.validators[i]\n for (j = 0; j < validator.rules.length; j++) {\n validatorReturnValue = validator.rules[j].method(\n validator.field,\n validator.rules[j].params\n )\n\n if (\n typeof validatorReturnValue === 'boolean' &&\n !validatorReturnValue\n ) {\n this.errors.push({\n fieldName: validator.fieldName,\n message: validator.rules[j].message\n })\n break\n } else if (typeof validatorReturnValue === 'string') {\n this.errors.push({\n fieldName: validatorReturnValue,\n message: validator.rules[j].message\n })\n break\n }\n }\n }\n return this.errors.length === 0\n }\n\n static entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n }\n}\n\n/**\n * @typedef {object} FormValidatorConfig\n * @property {HTMLElement} [summary] - HTML element to use for error summary\n */\n"],"names":["removeAttributeValue","el","attr","value","re","m","getAttribute","removeAttribute","RegExp","match","length","setAttribute","replace","addAttributeValue","test","FormValidator","constructor","form","config","HTMLFormElement","errors","validators","addEventListener","onSubmit","bind","summary","document","querySelector","originalTitle","title","escapeHtml","string","String","fromEntityMap","s","entityMap","resetTitle","updateTitle","showSummary","innerHTML","getSummaryHtml","classList","remove","focus","html","error","fieldName","message","hideSummary","add","event","removeInlineErrors","validate","preventDefault","showInlineErrors","showInlineError","errorSpan","createElement","id","control","fieldset","closest","fieldContainer","label","legend","after","removeInlineError","addValidator","rules","push","field","elements","validator","validatorReturnValue","i","j","method","params"],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ function removeAttributeValue(el, attr, value) {
2
+ let re, m;
3
+ if (el.getAttribute(attr)) {
4
+ if (el.getAttribute(attr) === value) {
5
+ el.removeAttribute(attr);
6
+ } else {
7
+ re = new RegExp(`(^|\\s)${value}(\\s|$)`);
8
+ m = el.getAttribute(attr).match(re);
9
+ if (m && m.length === 3) {
10
+ el.setAttribute(attr, el.getAttribute(attr).replace(re, m[1] && m[2] ? ' ' : ''));
11
+ }
12
+ }
13
+ }
14
+ }
15
+ function addAttributeValue(el, attr, value) {
16
+ let re;
17
+ if (!el.getAttribute(attr)) {
18
+ el.setAttribute(attr, value);
19
+ } else {
20
+ re = new RegExp(`(^|\\s)${value}(\\s|$)`);
21
+ if (!re.test(el.getAttribute(attr))) {
22
+ el.setAttribute(attr, `${el.getAttribute(attr)} ${value}`);
23
+ }
24
+ }
25
+ }
26
+
27
+ class FormValidator {
28
+ /**
29
+ * @param {Element | null} form - HTML element to use for form validator
30
+ * @param {FormValidatorConfig} [config] - Button menu config
31
+ */
32
+ constructor(form, config = {}) {
33
+ if (!form || !(form instanceof HTMLFormElement)) {
34
+ return this;
35
+ }
36
+ this.form = form;
37
+ this.errors = [];
38
+ this.validators = [];
39
+ this.form.addEventListener('submit', this.onSubmit.bind(this));
40
+ this.summary = config.summary || document.querySelector('.govuk-error-summary');
41
+ this.originalTitle = document.title;
42
+ }
43
+ escapeHtml(string) {
44
+ return String(string).replace(/[&<>"'`=/]/g, function fromEntityMap(s) {
45
+ return FormValidator.entityMap[s];
46
+ });
47
+ }
48
+ resetTitle() {
49
+ document.title = this.originalTitle;
50
+ }
51
+ updateTitle() {
52
+ document.title = `${this.errors.length} errors - ${document.title}`;
53
+ }
54
+ showSummary() {
55
+ this.summary.innerHTML = this.getSummaryHtml();
56
+ this.summary.classList.remove('moj-hidden');
57
+ this.summary.setAttribute('aria-labelledby', 'errorSummary-heading');
58
+ this.summary.focus();
59
+ }
60
+ getSummaryHtml() {
61
+ let html = '<h2 id="error-summary-title" class="govuk-error-summary__title">There is a problem</h2>';
62
+ html += '<div class="govuk-error-summary__body">';
63
+ html += '<ul class="govuk-list govuk-error-summary__list">';
64
+ for (const error of this.errors) {
65
+ html += '<li>';
66
+ html += `<a href="#${this.escapeHtml(error.fieldName)}">`;
67
+ html += this.escapeHtml(error.message);
68
+ html += '</a>';
69
+ html += '</li>';
70
+ }
71
+ html += '</ul>';
72
+ html += '</div>';
73
+ return html;
74
+ }
75
+ hideSummary() {
76
+ this.summary.classList.add('moj-hidden');
77
+ this.summary.removeAttribute('aria-labelledby');
78
+ }
79
+ onSubmit(event) {
80
+ this.removeInlineErrors();
81
+ this.hideSummary();
82
+ this.resetTitle();
83
+ if (!this.validate()) {
84
+ event.preventDefault();
85
+ this.updateTitle();
86
+ this.showSummary();
87
+ this.showInlineErrors();
88
+ }
89
+ }
90
+ showInlineErrors() {
91
+ for (const error of this.errors) {
92
+ this.showInlineError(error);
93
+ }
94
+ }
95
+ showInlineError(error) {
96
+ const errorSpan = document.createElement('span');
97
+ errorSpan.id = `${error.fieldName}-error`;
98
+ errorSpan.classList.add('govuk-error-message');
99
+ errorSpan.innerHTML = this.escapeHtml(error.message);
100
+ const control = document.querySelector(`#${error.fieldName}`);
101
+ const fieldset = control.closest('.govuk-fieldset');
102
+ const fieldContainer = (fieldset || control).closest('.govuk-form-group');
103
+ const label = fieldContainer.querySelector('label');
104
+ const legend = fieldContainer.querySelector('legend');
105
+ fieldContainer.classList.add('govuk-form-group--error');
106
+ if (fieldset && legend) {
107
+ legend.after(errorSpan);
108
+ fieldContainer.setAttribute('aria-invalid', 'true');
109
+ addAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
110
+ } else if (label && control) {
111
+ label.after(errorSpan);
112
+ control.setAttribute('aria-invalid', 'true');
113
+ addAttributeValue(control, 'aria-describedby', errorSpan.id);
114
+ }
115
+ }
116
+ removeInlineErrors() {
117
+ for (const error of this.errors) {
118
+ this.removeInlineError(error);
119
+ }
120
+ }
121
+ removeInlineError(error) {
122
+ const errorSpan = document.querySelector(`#${error.fieldName}-error`);
123
+ const control = document.querySelector(`#${error.fieldName}`);
124
+ const fieldset = control.closest('.govuk-fieldset');
125
+ const fieldContainer = (fieldset || control).closest('.govuk-form-group');
126
+ const label = fieldContainer.querySelector('label');
127
+ const legend = fieldContainer.querySelector('legend');
128
+ errorSpan.remove();
129
+ fieldContainer.classList.remove('govuk-form-group--error');
130
+ if (fieldset && legend) {
131
+ fieldContainer.removeAttribute('aria-invalid');
132
+ removeAttributeValue(fieldset, 'aria-describedby', errorSpan.id);
133
+ } else if (label && control) {
134
+ control.removeAttribute('aria-invalid');
135
+ removeAttributeValue(control, 'aria-describedby', errorSpan.id);
136
+ }
137
+ }
138
+ addValidator(fieldName, rules) {
139
+ this.validators.push({
140
+ fieldName,
141
+ rules,
142
+ field: this.form.elements[fieldName]
143
+ });
144
+ }
145
+ validate() {
146
+ this.errors = [];
147
+ let validator = null;
148
+ let validatorReturnValue = true;
149
+ let i;
150
+ let j;
151
+ for (i = 0; i < this.validators.length; i++) {
152
+ validator = this.validators[i];
153
+ for (j = 0; j < validator.rules.length; j++) {
154
+ validatorReturnValue = validator.rules[j].method(validator.field, validator.rules[j].params);
155
+ if (typeof validatorReturnValue === 'boolean' && !validatorReturnValue) {
156
+ this.errors.push({
157
+ fieldName: validator.fieldName,
158
+ message: validator.rules[j].message
159
+ });
160
+ break;
161
+ } else if (typeof validatorReturnValue === 'string') {
162
+ this.errors.push({
163
+ fieldName: validatorReturnValue,
164
+ message: validator.rules[j].message
165
+ });
166
+ break;
167
+ }
168
+ }
169
+ }
170
+ return this.errors.length === 0;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * @typedef {object} FormValidatorConfig
176
+ * @property {HTMLElement} [summary] - HTML element to use for error summary
177
+ */
178
+ FormValidator.entityMap = {
179
+ '&': '&amp;',
180
+ '<': '&lt;',
181
+ '>': '&gt;',
182
+ '"': '&quot;',
183
+ "'": '&#39;',
184
+ '/': '&#x2F;',
185
+ '`': '&#x60;',
186
+ '=': '&#x3D;'
187
+ };
188
+
189
+ export { FormValidator };
190
+ //# sourceMappingURL=form-validator.bundle.mjs.map