@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,223 @@
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 dragAndDropSupported() {
8
+ const div = document.createElement('div');
9
+ return typeof div.ondrop !== 'undefined';
10
+ }
11
+ function formDataSupported() {
12
+ return typeof FormData === 'function';
13
+ }
14
+ function fileApiSupported() {
15
+ const input = document.createElement('input');
16
+ input.type = 'file';
17
+ return typeof input.files !== 'undefined';
18
+ }
19
+
20
+ /* eslint-disable @typescript-eslint/no-empty-function */
21
+
22
+ class MultiFileUpload {
23
+ /**
24
+ * @param {MultiFileUploadConfig} [params] - Multi file upload config
25
+ */
26
+ constructor(params = {}) {
27
+ const {
28
+ container
29
+ } = params;
30
+ if (!container || !(container instanceof HTMLElement) || !(dragAndDropSupported() && formDataSupported() && fileApiSupported())) {
31
+ return this;
32
+ }
33
+ this.container = container;
34
+ this.container.classList.add('moj-multi-file-upload--enhanced');
35
+ this.defaultParams = {
36
+ uploadFileEntryHook: () => {},
37
+ uploadFileExitHook: () => {},
38
+ uploadFileErrorHook: () => {},
39
+ fileDeleteHook: () => {},
40
+ uploadStatusText: 'Uploading files, please wait',
41
+ dropzoneHintText: 'Drag and drop files here or',
42
+ dropzoneButtonText: 'Choose files'
43
+ };
44
+ this.params = Object.assign({}, this.defaultParams, params);
45
+ this.feedbackContainer = /** @type {HTMLDivElement} */
46
+ this.container.querySelector('.moj-multi-file__uploaded-files');
47
+ this.setupFileInput();
48
+ this.setupDropzone();
49
+ this.setupLabel();
50
+ this.setupStatusBox();
51
+ this.container.addEventListener('click', this.onFileDeleteClick.bind(this));
52
+ }
53
+ setupDropzone() {
54
+ this.dropzone = document.createElement('div');
55
+ this.dropzone.classList.add('moj-multi-file-upload__dropzone');
56
+ this.dropzone.addEventListener('dragover', this.onDragOver.bind(this));
57
+ this.dropzone.addEventListener('dragleave', this.onDragLeave.bind(this));
58
+ this.dropzone.addEventListener('drop', this.onDrop.bind(this));
59
+ this.fileInput.replaceWith(this.dropzone);
60
+ this.dropzone.appendChild(this.fileInput);
61
+ }
62
+ setupLabel() {
63
+ const label = document.createElement('label');
64
+ label.setAttribute('for', this.fileInput.id);
65
+ label.classList.add('govuk-button', 'govuk-button--secondary');
66
+ label.textContent = this.params.dropzoneButtonText;
67
+ const hint = document.createElement('p');
68
+ hint.classList.add('govuk-body');
69
+ hint.textContent = this.params.dropzoneHintText;
70
+ this.label = label;
71
+ this.dropzone.append(hint);
72
+ this.dropzone.append(label);
73
+ }
74
+ setupFileInput() {
75
+ this.fileInput = /** @type {HTMLInputElement} */
76
+ this.container.querySelector('.moj-multi-file-upload__input');
77
+ this.fileInput.addEventListener('change', this.onFileChange.bind(this));
78
+ this.fileInput.addEventListener('focus', this.onFileFocus.bind(this));
79
+ this.fileInput.addEventListener('blur', this.onFileBlur.bind(this));
80
+ }
81
+ setupStatusBox() {
82
+ this.status = document.createElement('div');
83
+ this.status.classList.add('govuk-visually-hidden');
84
+ this.status.setAttribute('aria-live', 'polite');
85
+ this.status.setAttribute('role', 'status');
86
+ this.dropzone.append(this.status);
87
+ }
88
+ onDragOver(event) {
89
+ event.preventDefault();
90
+ this.dropzone.classList.add('moj-multi-file-upload--dragover');
91
+ }
92
+ onDragLeave() {
93
+ this.dropzone.classList.remove('moj-multi-file-upload--dragover');
94
+ }
95
+ onDrop(event) {
96
+ event.preventDefault();
97
+ this.dropzone.classList.remove('moj-multi-file-upload--dragover');
98
+ this.feedbackContainer.classList.remove('moj-hidden');
99
+ this.status.textContent = this.params.uploadStatusText;
100
+ this.uploadFiles(event.dataTransfer.files);
101
+ }
102
+ uploadFiles(files) {
103
+ for (const file of Array.from(files)) {
104
+ this.uploadFile(file);
105
+ }
106
+ }
107
+ onFileChange() {
108
+ this.feedbackContainer.classList.remove('moj-hidden');
109
+ this.status.textContent = this.params.uploadStatusText;
110
+ this.uploadFiles(this.fileInput.files);
111
+ const fileInput = this.fileInput.cloneNode(true);
112
+ if (!fileInput || !(fileInput instanceof HTMLInputElement)) {
113
+ return;
114
+ }
115
+ fileInput.value = '';
116
+ this.fileInput.replaceWith(fileInput);
117
+ this.setupFileInput();
118
+ this.fileInput.focus();
119
+ }
120
+ onFileFocus() {
121
+ this.label.classList.add('moj-multi-file-upload--focused');
122
+ }
123
+ onFileBlur() {
124
+ this.label.classList.remove('moj-multi-file-upload--focused');
125
+ }
126
+ getSuccessHtml(success) {
127
+ return `<span class="moj-multi-file-upload__success"> <svg class="moj-banner__icon" fill="currentColor" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" height="25" width="25"><path d="M25,6.2L8.7,23.2L0,14.1l4-4.2l4.7,4.9L21,2L25,6.2z"/></svg>${success.messageHtml}</span>`;
128
+ }
129
+ getErrorHtml(error) {
130
+ return `<span class="moj-multi-file-upload__error"> <svg class="moj-banner__icon" fill="currentColor" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" height="25" width="25"><path d="M13.6,15.4h-2.3v-4.5h2.3V15.4z M13.6,19.8h-2.3v-2.2h2.3V19.8z M0,23.2h25L12.5,2L0,23.2z"/></svg>${error.message}</span>`;
131
+ }
132
+ getFileRow(file) {
133
+ const row = document.createElement('div');
134
+ row.classList.add('govuk-summary-list__row', 'moj-multi-file-upload__row');
135
+ row.innerHTML = `
136
+ <div class="govuk-summary-list__value moj-multi-file-upload__message">
137
+ <span class="moj-multi-file-upload__filename">${file.name}</span>
138
+ <span class="moj-multi-file-upload__progress">0%</span>
139
+ </div>
140
+ <div class="govuk-summary-list__actions moj-multi-file-upload__actions"></div>
141
+ `;
142
+ return row;
143
+ }
144
+ getDeleteButton(file) {
145
+ const button = document.createElement('button');
146
+ button.setAttribute('type', 'button');
147
+ button.setAttribute('name', 'delete');
148
+ button.setAttribute('value', file.filename);
149
+ button.classList.add('moj-multi-file-upload__delete', 'govuk-button', 'govuk-button--secondary', 'govuk-!-margin-bottom-0');
150
+ button.innerHTML = `Delete <span class="govuk-visually-hidden">${file.originalname}</span>`;
151
+ return button;
152
+ }
153
+ uploadFile(file) {
154
+ this.params.uploadFileEntryHook(this, file);
155
+ const item = this.getFileRow(file);
156
+ const message = item.querySelector('.moj-multi-file-upload__message');
157
+ const actions = item.querySelector('.moj-multi-file-upload__actions');
158
+ const progress = item.querySelector('.moj-multi-file-upload__progress');
159
+ const formData = new FormData();
160
+ formData.append('documents', file);
161
+ this.feedbackContainer.querySelector('.moj-multi-file-upload__list').append(item);
162
+ const xhr = new XMLHttpRequest();
163
+ const onLoad = () => {
164
+ if (xhr.status < 200 || xhr.status >= 300 || !('success' in xhr.response)) {
165
+ return onError();
166
+ }
167
+ message.innerHTML = this.getSuccessHtml(xhr.response.success);
168
+ this.status.textContent = xhr.response.success.messageText;
169
+ actions.append(this.getDeleteButton(xhr.response.file));
170
+ this.params.uploadFileExitHook(this, file, xhr, xhr.responseText);
171
+ };
172
+ const onError = () => {
173
+ const error = new Error(xhr.response && 'error' in xhr.response ? xhr.response.error.message : xhr.statusText || 'Upload failed');
174
+ message.innerHTML = this.getErrorHtml(error);
175
+ this.status.textContent = error.message;
176
+ this.params.uploadFileErrorHook(this, file, xhr, xhr.responseText, error);
177
+ };
178
+ xhr.addEventListener('load', onLoad);
179
+ xhr.addEventListener('error', onError);
180
+ xhr.upload.addEventListener('progress', event => {
181
+ if (!event.lengthComputable) {
182
+ return;
183
+ }
184
+ const percentComplete = Math.round(event.loaded / event.total * 100);
185
+ progress.textContent = ` ${percentComplete}%`;
186
+ });
187
+ xhr.open('POST', this.params.uploadUrl);
188
+ xhr.responseType = 'json';
189
+ xhr.send(formData);
190
+ }
191
+ onFileDeleteClick(event) {
192
+ const button = event.target;
193
+ if (!button || !(button instanceof HTMLButtonElement) || !button.classList.contains('moj-multi-file-upload__delete')) {
194
+ return;
195
+ }
196
+ event.preventDefault(); // if user refreshes page and then deletes
197
+
198
+ const xhr = new XMLHttpRequest();
199
+ xhr.addEventListener('load', () => {
200
+ if (xhr.status < 200 || xhr.status >= 300) {
201
+ return;
202
+ }
203
+ const rows = Array.from(this.feedbackContainer.querySelectorAll('.moj-multi-file-upload__row'));
204
+ if (rows.length === 1) {
205
+ this.feedbackContainer.classList.add('moj-hidden');
206
+ }
207
+ const row = rows.find(row => row.contains(button));
208
+ if (row) row.remove();
209
+ this.params.fileDeleteHook(this, undefined, xhr, xhr.responseText);
210
+ });
211
+ xhr.open('POST', this.params.deleteUrl);
212
+ xhr.setRequestHeader('Content-Type', 'application/json');
213
+ xhr.responseType = 'json';
214
+ xhr.send(JSON.stringify({
215
+ [button.name]: button.value
216
+ }));
217
+ }
218
+ }
219
+
220
+ exports.MultiFileUpload = MultiFileUpload;
221
+
222
+ }));
223
+ //# sourceMappingURL=multi-file-upload.bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-file-upload.bundle.js","sources":["../../../../src/moj/helpers.mjs","../../../../src/moj/components/multi-file-upload/multi-file-upload.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","/* eslint-disable @typescript-eslint/no-empty-function */\n\nimport {\n dragAndDropSupported,\n fileApiSupported,\n formDataSupported\n} from '../../helpers.mjs'\n\nexport class MultiFileUpload {\n /**\n * @param {MultiFileUploadConfig} [params] - Multi file upload config\n */\n constructor(params = {}) {\n const { container } = params\n\n if (\n !container ||\n !(container instanceof HTMLElement) ||\n !(dragAndDropSupported() && formDataSupported() && fileApiSupported())\n ) {\n return this\n }\n\n this.container = container\n this.container.classList.add('moj-multi-file-upload--enhanced')\n\n this.defaultParams = {\n uploadFileEntryHook: () => {},\n uploadFileExitHook: () => {},\n uploadFileErrorHook: () => {},\n fileDeleteHook: () => {},\n uploadStatusText: 'Uploading files, please wait',\n dropzoneHintText: 'Drag and drop files here or',\n dropzoneButtonText: 'Choose files'\n }\n\n this.params = Object.assign({}, this.defaultParams, params)\n\n this.feedbackContainer = /** @type {HTMLDivElement} */ (\n this.container.querySelector('.moj-multi-file__uploaded-files')\n )\n\n this.setupFileInput()\n this.setupDropzone()\n this.setupLabel()\n this.setupStatusBox()\n\n this.container.addEventListener('click', this.onFileDeleteClick.bind(this))\n }\n\n setupDropzone() {\n this.dropzone = document.createElement('div')\n this.dropzone.classList.add('moj-multi-file-upload__dropzone')\n\n this.dropzone.addEventListener('dragover', this.onDragOver.bind(this))\n this.dropzone.addEventListener('dragleave', this.onDragLeave.bind(this))\n this.dropzone.addEventListener('drop', this.onDrop.bind(this))\n\n this.fileInput.replaceWith(this.dropzone)\n this.dropzone.appendChild(this.fileInput)\n }\n\n setupLabel() {\n const label = document.createElement('label')\n label.setAttribute('for', this.fileInput.id)\n label.classList.add('govuk-button', 'govuk-button--secondary')\n label.textContent = this.params.dropzoneButtonText\n\n const hint = document.createElement('p')\n hint.classList.add('govuk-body')\n hint.textContent = this.params.dropzoneHintText\n\n this.label = label\n this.dropzone.append(hint)\n this.dropzone.append(label)\n }\n\n setupFileInput() {\n this.fileInput = /** @type {HTMLInputElement} */ (\n this.container.querySelector('.moj-multi-file-upload__input')\n )\n this.fileInput.addEventListener('change', this.onFileChange.bind(this))\n this.fileInput.addEventListener('focus', this.onFileFocus.bind(this))\n this.fileInput.addEventListener('blur', this.onFileBlur.bind(this))\n }\n\n setupStatusBox() {\n this.status = document.createElement('div')\n this.status.classList.add('govuk-visually-hidden')\n this.status.setAttribute('aria-live', 'polite')\n this.status.setAttribute('role', 'status')\n this.dropzone.append(this.status)\n }\n\n onDragOver(event) {\n event.preventDefault()\n this.dropzone.classList.add('moj-multi-file-upload--dragover')\n }\n\n onDragLeave() {\n this.dropzone.classList.remove('moj-multi-file-upload--dragover')\n }\n\n onDrop(event) {\n event.preventDefault()\n this.dropzone.classList.remove('moj-multi-file-upload--dragover')\n this.feedbackContainer.classList.remove('moj-hidden')\n this.status.textContent = this.params.uploadStatusText\n this.uploadFiles(event.dataTransfer.files)\n }\n\n uploadFiles(files) {\n for (const file of Array.from(files)) {\n this.uploadFile(file)\n }\n }\n\n onFileChange() {\n this.feedbackContainer.classList.remove('moj-hidden')\n this.status.textContent = this.params.uploadStatusText\n this.uploadFiles(this.fileInput.files)\n\n const fileInput = this.fileInput.cloneNode(true)\n if (!fileInput || !(fileInput instanceof HTMLInputElement)) {\n return\n }\n\n fileInput.value = ''\n this.fileInput.replaceWith(fileInput)\n\n this.setupFileInput()\n this.fileInput.focus()\n }\n\n onFileFocus() {\n this.label.classList.add('moj-multi-file-upload--focused')\n }\n\n onFileBlur() {\n this.label.classList.remove('moj-multi-file-upload--focused')\n }\n\n getSuccessHtml(success) {\n return `<span class=\"moj-multi-file-upload__success\"> <svg class=\"moj-banner__icon\" fill=\"currentColor\" role=\"presentation\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 25 25\" height=\"25\" width=\"25\"><path d=\"M25,6.2L8.7,23.2L0,14.1l4-4.2l4.7,4.9L21,2L25,6.2z\"/></svg>${success.messageHtml}</span>`\n }\n\n getErrorHtml(error) {\n return `<span class=\"moj-multi-file-upload__error\"> <svg class=\"moj-banner__icon\" fill=\"currentColor\" role=\"presentation\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 25 25\" height=\"25\" width=\"25\"><path d=\"M13.6,15.4h-2.3v-4.5h2.3V15.4z M13.6,19.8h-2.3v-2.2h2.3V19.8z M0,23.2h25L12.5,2L0,23.2z\"/></svg>${error.message}</span>`\n }\n\n getFileRow(file) {\n const row = document.createElement('div')\n\n row.classList.add('govuk-summary-list__row', 'moj-multi-file-upload__row')\n\n row.innerHTML = `\n <div class=\"govuk-summary-list__value moj-multi-file-upload__message\">\n <span class=\"moj-multi-file-upload__filename\">${file.name}</span>\n <span class=\"moj-multi-file-upload__progress\">0%</span>\n </div>\n <div class=\"govuk-summary-list__actions moj-multi-file-upload__actions\"></div>\n `\n\n return row\n }\n\n getDeleteButton(file) {\n const button = document.createElement('button')\n\n button.setAttribute('type', 'button')\n button.setAttribute('name', 'delete')\n button.setAttribute('value', file.filename)\n\n button.classList.add(\n 'moj-multi-file-upload__delete',\n 'govuk-button',\n 'govuk-button--secondary',\n 'govuk-!-margin-bottom-0'\n )\n\n button.innerHTML = `Delete <span class=\"govuk-visually-hidden\">${file.originalname}</span>`\n\n return button\n }\n\n uploadFile(file) {\n this.params.uploadFileEntryHook(this, file)\n\n const item = this.getFileRow(file)\n const message = item.querySelector('.moj-multi-file-upload__message')\n const actions = item.querySelector('.moj-multi-file-upload__actions')\n const progress = item.querySelector('.moj-multi-file-upload__progress')\n\n const formData = new FormData()\n formData.append('documents', file)\n\n this.feedbackContainer\n .querySelector('.moj-multi-file-upload__list')\n .append(item)\n\n const xhr = new XMLHttpRequest()\n\n const onLoad = () => {\n if (\n xhr.status < 200 ||\n xhr.status >= 300 ||\n !('success' in xhr.response)\n ) {\n return onError()\n }\n\n message.innerHTML = this.getSuccessHtml(xhr.response.success)\n this.status.textContent = xhr.response.success.messageText\n\n actions.append(this.getDeleteButton(xhr.response.file))\n this.params.uploadFileExitHook(this, file, xhr, xhr.responseText)\n }\n\n const onError = () => {\n const error = new Error(\n xhr.response && 'error' in xhr.response\n ? xhr.response.error.message\n : xhr.statusText || 'Upload failed'\n )\n\n message.innerHTML = this.getErrorHtml(error)\n this.status.textContent = error.message\n\n this.params.uploadFileErrorHook(this, file, xhr, xhr.responseText, error)\n }\n\n xhr.addEventListener('load', onLoad)\n xhr.addEventListener('error', onError)\n\n xhr.upload.addEventListener('progress', (event) => {\n if (!event.lengthComputable) {\n return\n }\n\n const percentComplete = Math.round((event.loaded / event.total) * 100)\n progress.textContent = ` ${percentComplete}%`\n })\n\n xhr.open('POST', this.params.uploadUrl)\n xhr.responseType = 'json'\n\n xhr.send(formData)\n }\n\n onFileDeleteClick(event) {\n const button = event.target\n\n if (\n !button ||\n !(button instanceof HTMLButtonElement) ||\n !button.classList.contains('moj-multi-file-upload__delete')\n ) {\n return\n }\n\n event.preventDefault() // if user refreshes page and then deletes\n\n const xhr = new XMLHttpRequest()\n\n xhr.addEventListener('load', () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n return\n }\n\n const rows = Array.from(\n this.feedbackContainer.querySelectorAll('.moj-multi-file-upload__row')\n )\n\n if (rows.length === 1) {\n this.feedbackContainer.classList.add('moj-hidden')\n }\n\n const row = rows.find((row) => row.contains(button))\n if (row) row.remove()\n\n this.params.fileDeleteHook(this, undefined, xhr, xhr.responseText)\n })\n\n xhr.open('POST', this.params.deleteUrl)\n xhr.setRequestHeader('Content-Type', 'application/json')\n xhr.responseType = 'json'\n\n xhr.send(\n JSON.stringify({\n [button.name]: button.value\n })\n )\n }\n}\n"],"names":["dragAndDropSupported","div","document","createElement","ondrop","formDataSupported","FormData","fileApiSupported","input","type","files","MultiFileUpload","constructor","params","container","HTMLElement","classList","add","defaultParams","uploadFileEntryHook","uploadFileExitHook","uploadFileErrorHook","fileDeleteHook","uploadStatusText","dropzoneHintText","dropzoneButtonText","Object","assign","feedbackContainer","querySelector","setupFileInput","setupDropzone","setupLabel","setupStatusBox","addEventListener","onFileDeleteClick","bind","dropzone","onDragOver","onDragLeave","onDrop","fileInput","replaceWith","appendChild","label","setAttribute","id","textContent","hint","append","onFileChange","onFileFocus","onFileBlur","status","event","preventDefault","remove","uploadFiles","dataTransfer","file","Array","from","uploadFile","cloneNode","HTMLInputElement","value","focus","getSuccessHtml","success","messageHtml","getErrorHtml","error","message","getFileRow","row","innerHTML","name","getDeleteButton","button","filename","originalname","item","actions","progress","formData","xhr","XMLHttpRequest","onLoad","response","onError","messageText","responseText","Error","statusText","upload","lengthComputable","percentComplete","Math","round","loaded","total","open","uploadUrl","responseType","send","target","HTMLButtonElement","contains","rows","querySelectorAll","length","find","undefined","deleteUrl","setRequestHeader","JSON","stringify"],"mappings":"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"}
@@ -0,0 +1,215 @@
1
+ function dragAndDropSupported() {
2
+ const div = document.createElement('div');
3
+ return typeof div.ondrop !== 'undefined';
4
+ }
5
+ function formDataSupported() {
6
+ return typeof FormData === 'function';
7
+ }
8
+ function fileApiSupported() {
9
+ const input = document.createElement('input');
10
+ input.type = 'file';
11
+ return typeof input.files !== 'undefined';
12
+ }
13
+
14
+ /* eslint-disable @typescript-eslint/no-empty-function */
15
+
16
+ class MultiFileUpload {
17
+ /**
18
+ * @param {MultiFileUploadConfig} [params] - Multi file upload config
19
+ */
20
+ constructor(params = {}) {
21
+ const {
22
+ container
23
+ } = params;
24
+ if (!container || !(container instanceof HTMLElement) || !(dragAndDropSupported() && formDataSupported() && fileApiSupported())) {
25
+ return this;
26
+ }
27
+ this.container = container;
28
+ this.container.classList.add('moj-multi-file-upload--enhanced');
29
+ this.defaultParams = {
30
+ uploadFileEntryHook: () => {},
31
+ uploadFileExitHook: () => {},
32
+ uploadFileErrorHook: () => {},
33
+ fileDeleteHook: () => {},
34
+ uploadStatusText: 'Uploading files, please wait',
35
+ dropzoneHintText: 'Drag and drop files here or',
36
+ dropzoneButtonText: 'Choose files'
37
+ };
38
+ this.params = Object.assign({}, this.defaultParams, params);
39
+ this.feedbackContainer = /** @type {HTMLDivElement} */
40
+ this.container.querySelector('.moj-multi-file__uploaded-files');
41
+ this.setupFileInput();
42
+ this.setupDropzone();
43
+ this.setupLabel();
44
+ this.setupStatusBox();
45
+ this.container.addEventListener('click', this.onFileDeleteClick.bind(this));
46
+ }
47
+ setupDropzone() {
48
+ this.dropzone = document.createElement('div');
49
+ this.dropzone.classList.add('moj-multi-file-upload__dropzone');
50
+ this.dropzone.addEventListener('dragover', this.onDragOver.bind(this));
51
+ this.dropzone.addEventListener('dragleave', this.onDragLeave.bind(this));
52
+ this.dropzone.addEventListener('drop', this.onDrop.bind(this));
53
+ this.fileInput.replaceWith(this.dropzone);
54
+ this.dropzone.appendChild(this.fileInput);
55
+ }
56
+ setupLabel() {
57
+ const label = document.createElement('label');
58
+ label.setAttribute('for', this.fileInput.id);
59
+ label.classList.add('govuk-button', 'govuk-button--secondary');
60
+ label.textContent = this.params.dropzoneButtonText;
61
+ const hint = document.createElement('p');
62
+ hint.classList.add('govuk-body');
63
+ hint.textContent = this.params.dropzoneHintText;
64
+ this.label = label;
65
+ this.dropzone.append(hint);
66
+ this.dropzone.append(label);
67
+ }
68
+ setupFileInput() {
69
+ this.fileInput = /** @type {HTMLInputElement} */
70
+ this.container.querySelector('.moj-multi-file-upload__input');
71
+ this.fileInput.addEventListener('change', this.onFileChange.bind(this));
72
+ this.fileInput.addEventListener('focus', this.onFileFocus.bind(this));
73
+ this.fileInput.addEventListener('blur', this.onFileBlur.bind(this));
74
+ }
75
+ setupStatusBox() {
76
+ this.status = document.createElement('div');
77
+ this.status.classList.add('govuk-visually-hidden');
78
+ this.status.setAttribute('aria-live', 'polite');
79
+ this.status.setAttribute('role', 'status');
80
+ this.dropzone.append(this.status);
81
+ }
82
+ onDragOver(event) {
83
+ event.preventDefault();
84
+ this.dropzone.classList.add('moj-multi-file-upload--dragover');
85
+ }
86
+ onDragLeave() {
87
+ this.dropzone.classList.remove('moj-multi-file-upload--dragover');
88
+ }
89
+ onDrop(event) {
90
+ event.preventDefault();
91
+ this.dropzone.classList.remove('moj-multi-file-upload--dragover');
92
+ this.feedbackContainer.classList.remove('moj-hidden');
93
+ this.status.textContent = this.params.uploadStatusText;
94
+ this.uploadFiles(event.dataTransfer.files);
95
+ }
96
+ uploadFiles(files) {
97
+ for (const file of Array.from(files)) {
98
+ this.uploadFile(file);
99
+ }
100
+ }
101
+ onFileChange() {
102
+ this.feedbackContainer.classList.remove('moj-hidden');
103
+ this.status.textContent = this.params.uploadStatusText;
104
+ this.uploadFiles(this.fileInput.files);
105
+ const fileInput = this.fileInput.cloneNode(true);
106
+ if (!fileInput || !(fileInput instanceof HTMLInputElement)) {
107
+ return;
108
+ }
109
+ fileInput.value = '';
110
+ this.fileInput.replaceWith(fileInput);
111
+ this.setupFileInput();
112
+ this.fileInput.focus();
113
+ }
114
+ onFileFocus() {
115
+ this.label.classList.add('moj-multi-file-upload--focused');
116
+ }
117
+ onFileBlur() {
118
+ this.label.classList.remove('moj-multi-file-upload--focused');
119
+ }
120
+ getSuccessHtml(success) {
121
+ return `<span class="moj-multi-file-upload__success"> <svg class="moj-banner__icon" fill="currentColor" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" height="25" width="25"><path d="M25,6.2L8.7,23.2L0,14.1l4-4.2l4.7,4.9L21,2L25,6.2z"/></svg>${success.messageHtml}</span>`;
122
+ }
123
+ getErrorHtml(error) {
124
+ return `<span class="moj-multi-file-upload__error"> <svg class="moj-banner__icon" fill="currentColor" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" height="25" width="25"><path d="M13.6,15.4h-2.3v-4.5h2.3V15.4z M13.6,19.8h-2.3v-2.2h2.3V19.8z M0,23.2h25L12.5,2L0,23.2z"/></svg>${error.message}</span>`;
125
+ }
126
+ getFileRow(file) {
127
+ const row = document.createElement('div');
128
+ row.classList.add('govuk-summary-list__row', 'moj-multi-file-upload__row');
129
+ row.innerHTML = `
130
+ <div class="govuk-summary-list__value moj-multi-file-upload__message">
131
+ <span class="moj-multi-file-upload__filename">${file.name}</span>
132
+ <span class="moj-multi-file-upload__progress">0%</span>
133
+ </div>
134
+ <div class="govuk-summary-list__actions moj-multi-file-upload__actions"></div>
135
+ `;
136
+ return row;
137
+ }
138
+ getDeleteButton(file) {
139
+ const button = document.createElement('button');
140
+ button.setAttribute('type', 'button');
141
+ button.setAttribute('name', 'delete');
142
+ button.setAttribute('value', file.filename);
143
+ button.classList.add('moj-multi-file-upload__delete', 'govuk-button', 'govuk-button--secondary', 'govuk-!-margin-bottom-0');
144
+ button.innerHTML = `Delete <span class="govuk-visually-hidden">${file.originalname}</span>`;
145
+ return button;
146
+ }
147
+ uploadFile(file) {
148
+ this.params.uploadFileEntryHook(this, file);
149
+ const item = this.getFileRow(file);
150
+ const message = item.querySelector('.moj-multi-file-upload__message');
151
+ const actions = item.querySelector('.moj-multi-file-upload__actions');
152
+ const progress = item.querySelector('.moj-multi-file-upload__progress');
153
+ const formData = new FormData();
154
+ formData.append('documents', file);
155
+ this.feedbackContainer.querySelector('.moj-multi-file-upload__list').append(item);
156
+ const xhr = new XMLHttpRequest();
157
+ const onLoad = () => {
158
+ if (xhr.status < 200 || xhr.status >= 300 || !('success' in xhr.response)) {
159
+ return onError();
160
+ }
161
+ message.innerHTML = this.getSuccessHtml(xhr.response.success);
162
+ this.status.textContent = xhr.response.success.messageText;
163
+ actions.append(this.getDeleteButton(xhr.response.file));
164
+ this.params.uploadFileExitHook(this, file, xhr, xhr.responseText);
165
+ };
166
+ const onError = () => {
167
+ const error = new Error(xhr.response && 'error' in xhr.response ? xhr.response.error.message : xhr.statusText || 'Upload failed');
168
+ message.innerHTML = this.getErrorHtml(error);
169
+ this.status.textContent = error.message;
170
+ this.params.uploadFileErrorHook(this, file, xhr, xhr.responseText, error);
171
+ };
172
+ xhr.addEventListener('load', onLoad);
173
+ xhr.addEventListener('error', onError);
174
+ xhr.upload.addEventListener('progress', event => {
175
+ if (!event.lengthComputable) {
176
+ return;
177
+ }
178
+ const percentComplete = Math.round(event.loaded / event.total * 100);
179
+ progress.textContent = ` ${percentComplete}%`;
180
+ });
181
+ xhr.open('POST', this.params.uploadUrl);
182
+ xhr.responseType = 'json';
183
+ xhr.send(formData);
184
+ }
185
+ onFileDeleteClick(event) {
186
+ const button = event.target;
187
+ if (!button || !(button instanceof HTMLButtonElement) || !button.classList.contains('moj-multi-file-upload__delete')) {
188
+ return;
189
+ }
190
+ event.preventDefault(); // if user refreshes page and then deletes
191
+
192
+ const xhr = new XMLHttpRequest();
193
+ xhr.addEventListener('load', () => {
194
+ if (xhr.status < 200 || xhr.status >= 300) {
195
+ return;
196
+ }
197
+ const rows = Array.from(this.feedbackContainer.querySelectorAll('.moj-multi-file-upload__row'));
198
+ if (rows.length === 1) {
199
+ this.feedbackContainer.classList.add('moj-hidden');
200
+ }
201
+ const row = rows.find(row => row.contains(button));
202
+ if (row) row.remove();
203
+ this.params.fileDeleteHook(this, undefined, xhr, xhr.responseText);
204
+ });
205
+ xhr.open('POST', this.params.deleteUrl);
206
+ xhr.setRequestHeader('Content-Type', 'application/json');
207
+ xhr.responseType = 'json';
208
+ xhr.send(JSON.stringify({
209
+ [button.name]: button.value
210
+ }));
211
+ }
212
+ }
213
+
214
+ export { MultiFileUpload };
215
+ //# sourceMappingURL=multi-file-upload.bundle.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-file-upload.bundle.mjs","sources":["../../../../src/moj/helpers.mjs","../../../../src/moj/components/multi-file-upload/multi-file-upload.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","/* eslint-disable @typescript-eslint/no-empty-function */\n\nimport {\n dragAndDropSupported,\n fileApiSupported,\n formDataSupported\n} from '../../helpers.mjs'\n\nexport class MultiFileUpload {\n /**\n * @param {MultiFileUploadConfig} [params] - Multi file upload config\n */\n constructor(params = {}) {\n const { container } = params\n\n if (\n !container ||\n !(container instanceof HTMLElement) ||\n !(dragAndDropSupported() && formDataSupported() && fileApiSupported())\n ) {\n return this\n }\n\n this.container = container\n this.container.classList.add('moj-multi-file-upload--enhanced')\n\n this.defaultParams = {\n uploadFileEntryHook: () => {},\n uploadFileExitHook: () => {},\n uploadFileErrorHook: () => {},\n fileDeleteHook: () => {},\n uploadStatusText: 'Uploading files, please wait',\n dropzoneHintText: 'Drag and drop files here or',\n dropzoneButtonText: 'Choose files'\n }\n\n this.params = Object.assign({}, this.defaultParams, params)\n\n this.feedbackContainer = /** @type {HTMLDivElement} */ (\n this.container.querySelector('.moj-multi-file__uploaded-files')\n )\n\n this.setupFileInput()\n this.setupDropzone()\n this.setupLabel()\n this.setupStatusBox()\n\n this.container.addEventListener('click', this.onFileDeleteClick.bind(this))\n }\n\n setupDropzone() {\n this.dropzone = document.createElement('div')\n this.dropzone.classList.add('moj-multi-file-upload__dropzone')\n\n this.dropzone.addEventListener('dragover', this.onDragOver.bind(this))\n this.dropzone.addEventListener('dragleave', this.onDragLeave.bind(this))\n this.dropzone.addEventListener('drop', this.onDrop.bind(this))\n\n this.fileInput.replaceWith(this.dropzone)\n this.dropzone.appendChild(this.fileInput)\n }\n\n setupLabel() {\n const label = document.createElement('label')\n label.setAttribute('for', this.fileInput.id)\n label.classList.add('govuk-button', 'govuk-button--secondary')\n label.textContent = this.params.dropzoneButtonText\n\n const hint = document.createElement('p')\n hint.classList.add('govuk-body')\n hint.textContent = this.params.dropzoneHintText\n\n this.label = label\n this.dropzone.append(hint)\n this.dropzone.append(label)\n }\n\n setupFileInput() {\n this.fileInput = /** @type {HTMLInputElement} */ (\n this.container.querySelector('.moj-multi-file-upload__input')\n )\n this.fileInput.addEventListener('change', this.onFileChange.bind(this))\n this.fileInput.addEventListener('focus', this.onFileFocus.bind(this))\n this.fileInput.addEventListener('blur', this.onFileBlur.bind(this))\n }\n\n setupStatusBox() {\n this.status = document.createElement('div')\n this.status.classList.add('govuk-visually-hidden')\n this.status.setAttribute('aria-live', 'polite')\n this.status.setAttribute('role', 'status')\n this.dropzone.append(this.status)\n }\n\n onDragOver(event) {\n event.preventDefault()\n this.dropzone.classList.add('moj-multi-file-upload--dragover')\n }\n\n onDragLeave() {\n this.dropzone.classList.remove('moj-multi-file-upload--dragover')\n }\n\n onDrop(event) {\n event.preventDefault()\n this.dropzone.classList.remove('moj-multi-file-upload--dragover')\n this.feedbackContainer.classList.remove('moj-hidden')\n this.status.textContent = this.params.uploadStatusText\n this.uploadFiles(event.dataTransfer.files)\n }\n\n uploadFiles(files) {\n for (const file of Array.from(files)) {\n this.uploadFile(file)\n }\n }\n\n onFileChange() {\n this.feedbackContainer.classList.remove('moj-hidden')\n this.status.textContent = this.params.uploadStatusText\n this.uploadFiles(this.fileInput.files)\n\n const fileInput = this.fileInput.cloneNode(true)\n if (!fileInput || !(fileInput instanceof HTMLInputElement)) {\n return\n }\n\n fileInput.value = ''\n this.fileInput.replaceWith(fileInput)\n\n this.setupFileInput()\n this.fileInput.focus()\n }\n\n onFileFocus() {\n this.label.classList.add('moj-multi-file-upload--focused')\n }\n\n onFileBlur() {\n this.label.classList.remove('moj-multi-file-upload--focused')\n }\n\n getSuccessHtml(success) {\n return `<span class=\"moj-multi-file-upload__success\"> <svg class=\"moj-banner__icon\" fill=\"currentColor\" role=\"presentation\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 25 25\" height=\"25\" width=\"25\"><path d=\"M25,6.2L8.7,23.2L0,14.1l4-4.2l4.7,4.9L21,2L25,6.2z\"/></svg>${success.messageHtml}</span>`\n }\n\n getErrorHtml(error) {\n return `<span class=\"moj-multi-file-upload__error\"> <svg class=\"moj-banner__icon\" fill=\"currentColor\" role=\"presentation\" focusable=\"false\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 25 25\" height=\"25\" width=\"25\"><path d=\"M13.6,15.4h-2.3v-4.5h2.3V15.4z M13.6,19.8h-2.3v-2.2h2.3V19.8z M0,23.2h25L12.5,2L0,23.2z\"/></svg>${error.message}</span>`\n }\n\n getFileRow(file) {\n const row = document.createElement('div')\n\n row.classList.add('govuk-summary-list__row', 'moj-multi-file-upload__row')\n\n row.innerHTML = `\n <div class=\"govuk-summary-list__value moj-multi-file-upload__message\">\n <span class=\"moj-multi-file-upload__filename\">${file.name}</span>\n <span class=\"moj-multi-file-upload__progress\">0%</span>\n </div>\n <div class=\"govuk-summary-list__actions moj-multi-file-upload__actions\"></div>\n `\n\n return row\n }\n\n getDeleteButton(file) {\n const button = document.createElement('button')\n\n button.setAttribute('type', 'button')\n button.setAttribute('name', 'delete')\n button.setAttribute('value', file.filename)\n\n button.classList.add(\n 'moj-multi-file-upload__delete',\n 'govuk-button',\n 'govuk-button--secondary',\n 'govuk-!-margin-bottom-0'\n )\n\n button.innerHTML = `Delete <span class=\"govuk-visually-hidden\">${file.originalname}</span>`\n\n return button\n }\n\n uploadFile(file) {\n this.params.uploadFileEntryHook(this, file)\n\n const item = this.getFileRow(file)\n const message = item.querySelector('.moj-multi-file-upload__message')\n const actions = item.querySelector('.moj-multi-file-upload__actions')\n const progress = item.querySelector('.moj-multi-file-upload__progress')\n\n const formData = new FormData()\n formData.append('documents', file)\n\n this.feedbackContainer\n .querySelector('.moj-multi-file-upload__list')\n .append(item)\n\n const xhr = new XMLHttpRequest()\n\n const onLoad = () => {\n if (\n xhr.status < 200 ||\n xhr.status >= 300 ||\n !('success' in xhr.response)\n ) {\n return onError()\n }\n\n message.innerHTML = this.getSuccessHtml(xhr.response.success)\n this.status.textContent = xhr.response.success.messageText\n\n actions.append(this.getDeleteButton(xhr.response.file))\n this.params.uploadFileExitHook(this, file, xhr, xhr.responseText)\n }\n\n const onError = () => {\n const error = new Error(\n xhr.response && 'error' in xhr.response\n ? xhr.response.error.message\n : xhr.statusText || 'Upload failed'\n )\n\n message.innerHTML = this.getErrorHtml(error)\n this.status.textContent = error.message\n\n this.params.uploadFileErrorHook(this, file, xhr, xhr.responseText, error)\n }\n\n xhr.addEventListener('load', onLoad)\n xhr.addEventListener('error', onError)\n\n xhr.upload.addEventListener('progress', (event) => {\n if (!event.lengthComputable) {\n return\n }\n\n const percentComplete = Math.round((event.loaded / event.total) * 100)\n progress.textContent = ` ${percentComplete}%`\n })\n\n xhr.open('POST', this.params.uploadUrl)\n xhr.responseType = 'json'\n\n xhr.send(formData)\n }\n\n onFileDeleteClick(event) {\n const button = event.target\n\n if (\n !button ||\n !(button instanceof HTMLButtonElement) ||\n !button.classList.contains('moj-multi-file-upload__delete')\n ) {\n return\n }\n\n event.preventDefault() // if user refreshes page and then deletes\n\n const xhr = new XMLHttpRequest()\n\n xhr.addEventListener('load', () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n return\n }\n\n const rows = Array.from(\n this.feedbackContainer.querySelectorAll('.moj-multi-file-upload__row')\n )\n\n if (rows.length === 1) {\n this.feedbackContainer.classList.add('moj-hidden')\n }\n\n const row = rows.find((row) => row.contains(button))\n if (row) row.remove()\n\n this.params.fileDeleteHook(this, undefined, xhr, xhr.responseText)\n })\n\n xhr.open('POST', this.params.deleteUrl)\n xhr.setRequestHeader('Content-Type', 'application/json')\n xhr.responseType = 'json'\n\n xhr.send(\n JSON.stringify({\n [button.name]: button.value\n })\n )\n }\n}\n"],"names":["dragAndDropSupported","div","document","createElement","ondrop","formDataSupported","FormData","fileApiSupported","input","type","files","MultiFileUpload","constructor","params","container","HTMLElement","classList","add","defaultParams","uploadFileEntryHook","uploadFileExitHook","uploadFileErrorHook","fileDeleteHook","uploadStatusText","dropzoneHintText","dropzoneButtonText","Object","assign","feedbackContainer","querySelector","setupFileInput","setupDropzone","setupLabel","setupStatusBox","addEventListener","onFileDeleteClick","bind","dropzone","onDragOver","onDragLeave","onDrop","fileInput","replaceWith","appendChild","label","setAttribute","id","textContent","hint","append","onFileChange","onFileFocus","onFileBlur","status","event","preventDefault","remove","uploadFiles","dataTransfer","file","Array","from","uploadFile","cloneNode","HTMLInputElement","value","focus","getSuccessHtml","success","messageHtml","getErrorHtml","error","message","getFileRow","row","innerHTML","name","getDeleteButton","button","filename","originalname","item","actions","progress","formData","xhr","XMLHttpRequest","onLoad","response","onError","messageText","responseText","Error","statusText","upload","lengthComputable","percentComplete","Math","round","loaded","total","open","uploadUrl","responseType","send","target","HTMLButtonElement","contains","rows","querySelectorAll","length","find","undefined","deleteUrl","setRequestHeader","JSON","stringify"],"mappings":"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"}