@ministryofjustice/frontend 4.0.1 → 5.1.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 (256) 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 +3010 -0
  5. package/moj/all.bundle.js.map +1 -0
  6. package/moj/all.bundle.mjs +3293 -0
  7. package/moj/all.bundle.mjs.map +1 -0
  8. package/moj/all.mjs +17 -110
  9. package/moj/all.mjs.map +1 -1
  10. package/moj/all.scss +3 -0
  11. package/moj/all.scss.map +1 -0
  12. package/moj/common/index.mjs +57 -0
  13. package/moj/common/index.mjs.map +1 -0
  14. package/moj/common/moj-frontend-version.mjs +14 -0
  15. package/moj/common/moj-frontend-version.mjs.map +1 -0
  16. package/moj/components/_all.scss +2 -0
  17. package/moj/components/_all.scss.map +1 -0
  18. package/moj/components/action-bar/_action-bar.scss +2 -0
  19. package/moj/components/action-bar/_action-bar.scss.map +1 -0
  20. package/moj/components/add-another/_add-another.scss +2 -0
  21. package/moj/components/add-another/_add-another.scss.map +1 -0
  22. package/moj/components/add-another/add-another.bundle.js +157 -0
  23. package/moj/components/add-another/add-another.bundle.js.map +1 -0
  24. package/moj/components/add-another/add-another.bundle.mjs +271 -0
  25. package/moj/components/add-another/add-another.bundle.mjs.map +1 -0
  26. package/moj/components/add-another/add-another.mjs +135 -91
  27. package/moj/components/add-another/add-another.mjs.map +1 -1
  28. package/moj/components/alert/_alert.scss +4 -0
  29. package/moj/components/alert/_alert.scss.map +1 -0
  30. package/moj/components/alert/alert.bundle.js +254 -0
  31. package/moj/components/alert/alert.bundle.js.map +1 -0
  32. package/moj/components/alert/alert.bundle.mjs +490 -0
  33. package/moj/components/alert/alert.bundle.mjs.map +1 -0
  34. package/moj/components/alert/alert.mjs +97 -218
  35. package/moj/components/alert/alert.mjs.map +1 -1
  36. package/moj/components/alert/{alert.spec.helper.js → alert.spec.helper.bundle.js} +1 -1
  37. package/moj/components/alert/alert.spec.helper.bundle.js.map +1 -0
  38. package/moj/components/alert/alert.spec.helper.bundle.mjs +67 -0
  39. package/moj/components/alert/alert.spec.helper.bundle.mjs.map +1 -0
  40. package/moj/components/alert/alert.spec.helper.mjs.map +1 -1
  41. package/moj/components/badge/_badge.scss +2 -0
  42. package/moj/components/badge/_badge.scss.map +1 -0
  43. package/moj/components/banner/_banner.scss +2 -0
  44. package/moj/components/banner/_banner.scss.map +1 -0
  45. package/moj/components/button-menu/README.md +12 -6
  46. package/moj/components/button-menu/_button-menu.scss +4 -1
  47. package/moj/components/button-menu/_button-menu.scss.map +1 -0
  48. package/moj/components/button-menu/button-menu.bundle.js +270 -0
  49. package/moj/components/button-menu/button-menu.bundle.js.map +1 -0
  50. package/moj/components/button-menu/button-menu.bundle.mjs +506 -0
  51. package/moj/components/button-menu/button-menu.bundle.mjs.map +1 -0
  52. package/moj/components/button-menu/button-menu.mjs +214 -280
  53. package/moj/components/button-menu/button-menu.mjs.map +1 -1
  54. package/moj/components/cookie-banner/_cookie-banner.scss +2 -0
  55. package/moj/components/cookie-banner/_cookie-banner.scss.map +1 -0
  56. package/moj/components/currency-input/_currency-input.scss +2 -0
  57. package/moj/components/currency-input/_currency-input.scss.map +1 -0
  58. package/moj/components/date-picker/_date-picker.scss +2 -0
  59. package/moj/components/date-picker/_date-picker.scss.map +1 -0
  60. package/moj/components/date-picker/date-picker.bundle.js +804 -0
  61. package/moj/components/date-picker/date-picker.bundle.js.map +1 -0
  62. package/moj/components/date-picker/date-picker.bundle.mjs +1040 -0
  63. package/moj/components/date-picker/date-picker.bundle.mjs.map +1 -0
  64. package/moj/components/date-picker/date-picker.mjs +663 -827
  65. package/moj/components/date-picker/date-picker.mjs.map +1 -1
  66. package/moj/components/filter/_filter.scss +2 -0
  67. package/moj/components/filter/_filter.scss.map +1 -0
  68. package/moj/components/filter/template.njk +1 -1
  69. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js +185 -0
  70. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.js.map +1 -0
  71. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs +421 -0
  72. package/moj/components/filter-toggle-button/filter-toggle-button.bundle.mjs.map +1 -0
  73. package/moj/components/filter-toggle-button/filter-toggle-button.mjs +166 -81
  74. package/moj/components/filter-toggle-button/filter-toggle-button.mjs.map +1 -1
  75. package/moj/components/form-validator/form-validator.bundle.js +288 -0
  76. package/moj/components/form-validator/form-validator.bundle.js.map +1 -0
  77. package/moj/components/form-validator/form-validator.bundle.mjs +524 -0
  78. package/moj/components/form-validator/form-validator.bundle.mjs.map +1 -0
  79. package/moj/components/form-validator/form-validator.mjs +226 -149
  80. package/moj/components/form-validator/form-validator.mjs.map +1 -1
  81. package/moj/components/header/_header.scss +2 -0
  82. package/moj/components/header/_header.scss.map +1 -0
  83. package/moj/components/identity-bar/_identity-bar.scss +2 -0
  84. package/moj/components/identity-bar/_identity-bar.scss.map +1 -0
  85. package/moj/components/interruption-card/_interruption-card.scss +2 -0
  86. package/moj/components/interruption-card/_interruption-card.scss.map +1 -0
  87. package/moj/components/messages/_messages.scss +2 -0
  88. package/moj/components/messages/_messages.scss.map +1 -0
  89. package/moj/components/multi-file-upload/_multi-file-upload.scss +2 -0
  90. package/moj/components/multi-file-upload/_multi-file-upload.scss.map +1 -0
  91. package/moj/components/multi-file-upload/multi-file-upload.bundle.js +397 -0
  92. package/moj/components/multi-file-upload/multi-file-upload.bundle.js.map +1 -0
  93. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs +633 -0
  94. package/moj/components/multi-file-upload/multi-file-upload.bundle.mjs.map +1 -0
  95. package/moj/components/multi-file-upload/multi-file-upload.mjs +384 -213
  96. package/moj/components/multi-file-upload/multi-file-upload.mjs.map +1 -1
  97. package/moj/components/multi-file-upload/template.njk +1 -1
  98. package/moj/components/multi-select/_multi-select.scss +2 -0
  99. package/moj/components/multi-select/_multi-select.scss.map +1 -0
  100. package/moj/components/multi-select/multi-select.bundle.js +143 -0
  101. package/moj/components/multi-select/multi-select.bundle.js.map +1 -0
  102. package/moj/components/multi-select/multi-select.bundle.mjs +379 -0
  103. package/moj/components/multi-select/multi-select.bundle.mjs.map +1 -0
  104. package/moj/components/multi-select/multi-select.mjs +123 -64
  105. package/moj/components/multi-select/multi-select.mjs.map +1 -1
  106. package/moj/components/notification-badge/_notification-badge.scss +2 -0
  107. package/moj/components/notification-badge/_notification-badge.scss.map +1 -0
  108. package/moj/components/organisation-switcher/_organisation-switcher.scss +2 -0
  109. package/moj/components/organisation-switcher/_organisation-switcher.scss.map +1 -0
  110. package/moj/components/page-header-actions/_page-header-actions.scss +2 -0
  111. package/moj/components/page-header-actions/_page-header-actions.scss.map +1 -0
  112. package/moj/components/pagination/_pagination.scss +2 -2
  113. package/moj/components/pagination/_pagination.scss.map +1 -0
  114. package/moj/components/password-reveal/_password-reveal.scss +5 -1
  115. package/moj/components/password-reveal/_password-reveal.scss.map +1 -0
  116. package/moj/components/password-reveal/password-reveal.bundle.js +52 -0
  117. package/moj/components/password-reveal/password-reveal.bundle.js.map +1 -0
  118. package/moj/components/password-reveal/password-reveal.bundle.mjs +166 -0
  119. package/moj/components/password-reveal/password-reveal.bundle.mjs.map +1 -0
  120. package/moj/components/password-reveal/password-reveal.mjs +39 -29
  121. package/moj/components/password-reveal/password-reveal.mjs.map +1 -1
  122. package/moj/components/primary-navigation/_primary-navigation.scss +2 -0
  123. package/moj/components/primary-navigation/_primary-navigation.scss.map +1 -0
  124. package/moj/components/progress-bar/_progress-bar.scss +2 -0
  125. package/moj/components/progress-bar/_progress-bar.scss.map +1 -0
  126. package/moj/components/rich-text-editor/README.md +16 -9
  127. package/moj/components/rich-text-editor/_rich-text-editor.scss +2 -0
  128. package/moj/components/rich-text-editor/_rich-text-editor.scss.map +1 -0
  129. package/moj/components/rich-text-editor/rich-text-editor.bundle.js +210 -0
  130. package/moj/components/rich-text-editor/rich-text-editor.bundle.js.map +1 -0
  131. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs +446 -0
  132. package/moj/components/rich-text-editor/rich-text-editor.bundle.mjs.map +1 -0
  133. package/moj/components/rich-text-editor/rich-text-editor.mjs +186 -140
  134. package/moj/components/rich-text-editor/rich-text-editor.mjs.map +1 -1
  135. package/moj/components/search/_search.scss +2 -0
  136. package/moj/components/search/_search.scss.map +1 -0
  137. package/moj/components/search-toggle/{search-toggle.scss → _search-toggle.scss} +2 -0
  138. package/moj/components/search-toggle/_search-toggle.scss.map +1 -0
  139. package/moj/components/search-toggle/search-toggle.bundle.js +122 -0
  140. package/moj/components/search-toggle/search-toggle.bundle.js.map +1 -0
  141. package/moj/components/search-toggle/search-toggle.bundle.mjs +358 -0
  142. package/moj/components/search-toggle/search-toggle.bundle.mjs.map +1 -0
  143. package/moj/components/search-toggle/search-toggle.mjs +104 -43
  144. package/moj/components/search-toggle/search-toggle.mjs.map +1 -1
  145. package/moj/components/side-navigation/_side-navigation.scss +2 -0
  146. package/moj/components/side-navigation/_side-navigation.scss.map +1 -0
  147. package/moj/components/sortable-table/_sortable-table.scss +2 -2
  148. package/moj/components/sortable-table/_sortable-table.scss.map +1 -0
  149. package/moj/components/sortable-table/sortable-table.bundle.js +202 -0
  150. package/moj/components/sortable-table/sortable-table.bundle.js.map +1 -0
  151. package/moj/components/sortable-table/sortable-table.bundle.mjs +438 -0
  152. package/moj/components/sortable-table/sortable-table.bundle.mjs.map +1 -0
  153. package/moj/components/sortable-table/sortable-table.mjs +179 -122
  154. package/moj/components/sortable-table/sortable-table.mjs.map +1 -1
  155. package/moj/components/sub-navigation/_sub-navigation.scss +2 -0
  156. package/moj/components/sub-navigation/_sub-navigation.scss.map +1 -0
  157. package/moj/components/tag/_tag.scss +2 -0
  158. package/moj/components/tag/_tag.scss.map +1 -0
  159. package/moj/components/task-list/_task-list.scss +2 -0
  160. package/moj/components/task-list/_task-list.scss.map +1 -0
  161. package/moj/components/ticket-panel/_ticket-panel.scss +2 -0
  162. package/moj/components/ticket-panel/_ticket-panel.scss.map +1 -0
  163. package/moj/components/timeline/_timeline.scss +2 -0
  164. package/moj/components/timeline/_timeline.scss.map +1 -0
  165. package/moj/core/_all.scss +3 -0
  166. package/moj/core/_all.scss.map +1 -0
  167. package/moj/core/_moj-frontend-properties.scss +7 -0
  168. package/moj/core/_moj-frontend-properties.scss.map +1 -0
  169. package/moj/filters/all.js +44 -22
  170. package/moj/filters/prototype-kit-13-filters.js +4 -3
  171. package/moj/helpers/_all.scss +2 -0
  172. package/moj/helpers/_all.scss.map +1 -0
  173. package/moj/helpers/_hidden.scss +2 -0
  174. package/moj/helpers/_hidden.scss.map +1 -0
  175. package/moj/helpers/_links.scss +2 -0
  176. package/moj/helpers/_links.scss.map +1 -0
  177. package/moj/helpers.bundle.js +140 -0
  178. package/moj/helpers.bundle.js.map +1 -0
  179. package/moj/helpers.bundle.mjs +128 -0
  180. package/moj/helpers.bundle.mjs.map +1 -0
  181. package/moj/helpers.mjs +50 -77
  182. package/moj/helpers.mjs.map +1 -1
  183. package/moj/init.js +11 -2
  184. package/moj/moj-frontend.min.css +1 -1
  185. package/moj/moj-frontend.min.css.map +1 -1
  186. package/moj/moj-frontend.min.js +1 -1
  187. package/moj/moj-frontend.min.js.map +1 -1
  188. package/moj/objects/_all.scss +2 -0
  189. package/moj/objects/_all.scss.map +1 -0
  190. package/moj/objects/_button-group.scss +2 -0
  191. package/moj/objects/_button-group.scss.map +1 -0
  192. package/moj/objects/_filter-layout.scss +2 -0
  193. package/moj/objects/_filter-layout.scss.map +1 -0
  194. package/moj/objects/_scrollable-pane.scss +2 -0
  195. package/moj/objects/_scrollable-pane.scss.map +1 -0
  196. package/moj/objects/_width-container.scss +2 -0
  197. package/moj/objects/_width-container.scss.map +1 -0
  198. package/moj/settings/_all.scss +2 -0
  199. package/moj/settings/_all.scss.map +1 -0
  200. package/moj/settings/_assets.scss +2 -0
  201. package/moj/settings/_assets.scss.map +1 -0
  202. package/moj/settings/_colours.scss +2 -0
  203. package/moj/settings/_colours.scss.map +1 -0
  204. package/moj/settings/_measurements.scss +2 -0
  205. package/moj/settings/_measurements.scss.map +1 -0
  206. package/moj/settings/_typography.scss +2 -0
  207. package/moj/settings/_typography.scss.map +1 -0
  208. package/moj/template.njk +13 -0
  209. package/moj/utilities/_all.scss +2 -0
  210. package/moj/utilities/_all.scss.map +1 -0
  211. package/moj/utilities/_hidden.scss +2 -0
  212. package/moj/utilities/_hidden.scss.map +1 -0
  213. package/moj/utilities/_width-container.scss +2 -0
  214. package/moj/utilities/_width-container.scss.map +1 -0
  215. package/moj/vendor/govuk-frontend/_base.scss +2 -0
  216. package/moj/vendor/govuk-frontend/_base.scss.map +1 -0
  217. package/moj/vendor/govuk-frontend/_index.scss +2 -0
  218. package/moj/vendor/govuk-frontend/_index.scss.map +1 -0
  219. package/package.json +5 -6
  220. package/moj/all.jquery.min.js +0 -1
  221. package/moj/all.jquery.min.js.map +0 -1
  222. package/moj/all.js +0 -2662
  223. package/moj/all.js.map +0 -1
  224. package/moj/components/add-another/add-another.js +0 -115
  225. package/moj/components/add-another/add-another.js.map +0 -1
  226. package/moj/components/alert/alert.js +0 -356
  227. package/moj/components/alert/alert.js.map +0 -1
  228. package/moj/components/alert/alert.spec.helper.js.map +0 -1
  229. package/moj/components/button-menu/button-menu.js +0 -338
  230. package/moj/components/button-menu/button-menu.js.map +0 -1
  231. package/moj/components/date-picker/date-picker.js +0 -970
  232. package/moj/components/date-picker/date-picker.js.map +0 -1
  233. package/moj/components/filter-toggle-button/filter-toggle-button.js +0 -102
  234. package/moj/components/filter-toggle-button/filter-toggle-button.js.map +0 -1
  235. package/moj/components/form-validator/form-validator.js +0 -205
  236. package/moj/components/form-validator/form-validator.js.map +0 -1
  237. package/moj/components/multi-file-upload/multi-file-upload.js +0 -241
  238. package/moj/components/multi-file-upload/multi-file-upload.js.map +0 -1
  239. package/moj/components/multi-select/multi-select.js +0 -86
  240. package/moj/components/multi-select/multi-select.js.map +0 -1
  241. package/moj/components/password-reveal/password-reveal.js +0 -44
  242. package/moj/components/password-reveal/password-reveal.js.map +0 -1
  243. package/moj/components/rich-text-editor/rich-text-editor.js +0 -166
  244. package/moj/components/rich-text-editor/rich-text-editor.js.map +0 -1
  245. package/moj/components/search-toggle/search-toggle.js +0 -63
  246. package/moj/components/search-toggle/search-toggle.js.map +0 -1
  247. package/moj/components/sortable-table/sortable-table.js +0 -147
  248. package/moj/components/sortable-table/sortable-table.js.map +0 -1
  249. package/moj/helpers.js +0 -200
  250. package/moj/helpers.js.map +0 -1
  251. package/moj/vendor/html5shiv.js +0 -326
  252. package/moj/vendor/jquery.js +0 -9300
  253. package/moj/version.js +0 -12
  254. package/moj/version.js.map +0 -1
  255. package/moj/version.mjs +0 -4
  256. package/moj/version.mjs.map +0 -1
@@ -0,0 +1,633 @@
1
+ function isInitialised($root, moduleName) {
2
+ return $root instanceof HTMLElement && $root.hasAttribute(`data-${moduleName}-init`);
3
+ }
4
+
5
+ /**
6
+ * Checks if GOV.UK Frontend is supported on this page
7
+ *
8
+ * Some browsers will load and run our JavaScript but GOV.UK Frontend
9
+ * won't be supported.
10
+ *
11
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
12
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
13
+ */
14
+ function isSupported($scope = document.body) {
15
+ if (!$scope) {
16
+ return false;
17
+ }
18
+ return $scope.classList.contains('govuk-frontend-supported');
19
+ }
20
+ function isArray(option) {
21
+ return Array.isArray(option);
22
+ }
23
+ function isObject(option) {
24
+ return !!option && typeof option === 'object' && !isArray(option);
25
+ }
26
+ function formatErrorMessage(Component, message) {
27
+ return `${Component.moduleName}: ${message}`;
28
+ }
29
+
30
+ class GOVUKFrontendError extends Error {
31
+ constructor(...args) {
32
+ super(...args);
33
+ this.name = 'GOVUKFrontendError';
34
+ }
35
+ }
36
+ class SupportError extends GOVUKFrontendError {
37
+ /**
38
+ * Checks if GOV.UK Frontend is supported on this page
39
+ *
40
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
41
+ */
42
+ constructor($scope = document.body) {
43
+ const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
44
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
45
+ this.name = 'SupportError';
46
+ }
47
+ }
48
+ class ConfigError extends GOVUKFrontendError {
49
+ constructor(...args) {
50
+ super(...args);
51
+ this.name = 'ConfigError';
52
+ }
53
+ }
54
+ class ElementError extends GOVUKFrontendError {
55
+ constructor(messageOrOptions) {
56
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
57
+ if (typeof messageOrOptions === 'object') {
58
+ const {
59
+ component,
60
+ identifier,
61
+ element,
62
+ expectedType
63
+ } = messageOrOptions;
64
+ message = identifier;
65
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
66
+ message = formatErrorMessage(component, message);
67
+ }
68
+ super(message);
69
+ this.name = 'ElementError';
70
+ }
71
+ }
72
+ class InitError extends GOVUKFrontendError {
73
+ constructor(componentOrMessage) {
74
+ const message = typeof componentOrMessage === 'string' ? componentOrMessage : formatErrorMessage(componentOrMessage, `Root element (\`$root\`) already initialised`);
75
+ super(message);
76
+ this.name = 'InitError';
77
+ }
78
+ }
79
+
80
+ class Component {
81
+ /**
82
+ * Returns the root element of the component
83
+ *
84
+ * @protected
85
+ * @returns {RootElementType} - the root element of component
86
+ */
87
+ get $root() {
88
+ return this._$root;
89
+ }
90
+ constructor($root) {
91
+ this._$root = void 0;
92
+ const childConstructor = this.constructor;
93
+ if (typeof childConstructor.moduleName !== 'string') {
94
+ throw new InitError(`\`moduleName\` not defined in component`);
95
+ }
96
+ if (!($root instanceof childConstructor.elementType)) {
97
+ throw new ElementError({
98
+ element: $root,
99
+ component: childConstructor,
100
+ identifier: 'Root element (`$root`)',
101
+ expectedType: childConstructor.elementType.name
102
+ });
103
+ } else {
104
+ this._$root = $root;
105
+ }
106
+ childConstructor.checkSupport();
107
+ this.checkInitialised();
108
+ const moduleName = childConstructor.moduleName;
109
+ this.$root.setAttribute(`data-${moduleName}-init`, '');
110
+ }
111
+ checkInitialised() {
112
+ const constructor = this.constructor;
113
+ const moduleName = constructor.moduleName;
114
+ if (moduleName && isInitialised(this.$root, moduleName)) {
115
+ throw new InitError(constructor);
116
+ }
117
+ }
118
+ static checkSupport() {
119
+ if (!isSupported()) {
120
+ throw new SupportError();
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @typedef ChildClass
127
+ * @property {string} moduleName - The module name that'll be looked for in the DOM when initialising the component
128
+ */
129
+
130
+ /**
131
+ * @typedef {typeof Component & ChildClass} ChildClassConstructor
132
+ */
133
+ Component.elementType = HTMLElement;
134
+
135
+ const configOverride = Symbol.for('configOverride');
136
+ class ConfigurableComponent extends Component {
137
+ [configOverride](param) {
138
+ return {};
139
+ }
140
+
141
+ /**
142
+ * Returns the root element of the component
143
+ *
144
+ * @protected
145
+ * @returns {ConfigurationType} - the root element of component
146
+ */
147
+ get config() {
148
+ return this._config;
149
+ }
150
+ constructor($root, config) {
151
+ super($root);
152
+ this._config = void 0;
153
+ const childConstructor = this.constructor;
154
+ if (!isObject(childConstructor.defaults)) {
155
+ throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
156
+ }
157
+ const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
158
+ this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
159
+ }
160
+ }
161
+ function normaliseString(value, property) {
162
+ const trimmedValue = value ? value.trim() : '';
163
+ let output;
164
+ let outputType = property == null ? void 0 : property.type;
165
+ if (!outputType) {
166
+ if (['true', 'false'].includes(trimmedValue)) {
167
+ outputType = 'boolean';
168
+ }
169
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
170
+ outputType = 'number';
171
+ }
172
+ }
173
+ switch (outputType) {
174
+ case 'boolean':
175
+ output = trimmedValue === 'true';
176
+ break;
177
+ case 'number':
178
+ output = Number(trimmedValue);
179
+ break;
180
+ default:
181
+ output = value;
182
+ }
183
+ return output;
184
+ }
185
+ function normaliseDataset(Component, dataset) {
186
+ if (!isObject(Component.schema)) {
187
+ throw new ConfigError(formatErrorMessage(Component, 'Config passed as parameter into constructor but no schema defined'));
188
+ }
189
+ const out = {};
190
+ const entries = Object.entries(Component.schema.properties);
191
+ for (const entry of entries) {
192
+ const [namespace, property] = entry;
193
+ const field = namespace.toString();
194
+ if (field in dataset) {
195
+ out[field] = normaliseString(dataset[field], property);
196
+ }
197
+ if ((property == null ? void 0 : property.type) === 'object') {
198
+ out[field] = extractConfigByNamespace(Component.schema, dataset, namespace);
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+ function mergeConfigs(...configObjects) {
204
+ const formattedConfigObject = {};
205
+ for (const configObject of configObjects) {
206
+ for (const key of Object.keys(configObject)) {
207
+ const option = formattedConfigObject[key];
208
+ const override = configObject[key];
209
+ if (isObject(option) && isObject(override)) {
210
+ formattedConfigObject[key] = mergeConfigs(option, override);
211
+ } else {
212
+ formattedConfigObject[key] = override;
213
+ }
214
+ }
215
+ }
216
+ return formattedConfigObject;
217
+ }
218
+ function extractConfigByNamespace(schema, dataset, namespace) {
219
+ const property = schema.properties[namespace];
220
+ if ((property == null ? void 0 : property.type) !== 'object') {
221
+ return;
222
+ }
223
+ const newObject = {
224
+ [namespace]: {}
225
+ };
226
+ for (const [key, value] of Object.entries(dataset)) {
227
+ let current = newObject;
228
+ const keyParts = key.split('.');
229
+ for (const [index, name] of keyParts.entries()) {
230
+ if (isObject(current)) {
231
+ if (index < keyParts.length - 1) {
232
+ if (!isObject(current[name])) {
233
+ current[name] = {};
234
+ }
235
+ current = current[name];
236
+ } else if (key !== namespace) {
237
+ current[name] = normaliseString(value);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ return newObject[namespace];
243
+ }
244
+
245
+ /* eslint-disable @typescript-eslint/no-empty-function */
246
+
247
+
248
+ /**
249
+ * @augments {ConfigurableComponent<MultiFileUploadConfig>}
250
+ */
251
+ class MultiFileUpload extends ConfigurableComponent {
252
+ /**
253
+ * @param {Element | null} $root - HTML element to use for multi file upload
254
+ * @param {MultiFileUploadConfig} [config] - Multi file upload config
255
+ */
256
+ constructor($root, config = {}) {
257
+ var _this$config$feedback;
258
+ super($root, config);
259
+ if (!MultiFileUpload.isSupported()) {
260
+ return this;
261
+ }
262
+ const $feedbackContainer = (_this$config$feedback = this.config.feedbackContainer.element) != null ? _this$config$feedback : this.$root.querySelector(this.config.feedbackContainer.selector);
263
+ if (!$feedbackContainer || !($feedbackContainer instanceof HTMLElement)) {
264
+ return this;
265
+ }
266
+ this.$feedbackContainer = $feedbackContainer;
267
+ this.setupFileInput();
268
+ this.setupDropzone();
269
+ this.setupLabel();
270
+ this.setupStatusBox();
271
+ this.$root.addEventListener('click', this.onFileDeleteClick.bind(this));
272
+ this.$root.classList.add('moj-multi-file-upload--enhanced');
273
+ }
274
+ setupDropzone() {
275
+ this.$dropzone = document.createElement('div');
276
+ this.$dropzone.classList.add('moj-multi-file-upload__dropzone');
277
+ this.$dropzone.addEventListener('dragover', this.onDragOver.bind(this));
278
+ this.$dropzone.addEventListener('dragleave', this.onDragLeave.bind(this));
279
+ this.$dropzone.addEventListener('drop', this.onDrop.bind(this));
280
+ this.$fileInput.replaceWith(this.$dropzone);
281
+ this.$dropzone.appendChild(this.$fileInput);
282
+ }
283
+ setupLabel() {
284
+ const $label = document.createElement('label');
285
+ $label.setAttribute('for', this.$fileInput.id);
286
+ $label.classList.add('govuk-button', 'govuk-button--secondary');
287
+ $label.textContent = this.config.dropzoneButtonText;
288
+ const $hint = document.createElement('p');
289
+ $hint.classList.add('govuk-body');
290
+ $hint.textContent = this.config.dropzoneHintText;
291
+ this.$label = $label;
292
+ this.$dropzone.append($hint);
293
+ this.$dropzone.append($label);
294
+ }
295
+ setupFileInput() {
296
+ this.$fileInput = /** @type {HTMLInputElement} */
297
+ this.$root.querySelector('.moj-multi-file-upload__input');
298
+ this.$fileInput.addEventListener('change', this.onFileChange.bind(this));
299
+ this.$fileInput.addEventListener('focus', this.onFileFocus.bind(this));
300
+ this.$fileInput.addEventListener('blur', this.onFileBlur.bind(this));
301
+ }
302
+ setupStatusBox() {
303
+ this.$status = document.createElement('div');
304
+ this.$status.classList.add('govuk-visually-hidden');
305
+ this.$status.setAttribute('aria-live', 'polite');
306
+ this.$status.setAttribute('role', 'status');
307
+ this.$dropzone.append(this.$status);
308
+ }
309
+
310
+ /**
311
+ * @param {DragEvent} event - Drag event
312
+ */
313
+ onDragOver(event) {
314
+ event.preventDefault();
315
+ this.$dropzone.classList.add('moj-multi-file-upload--dragover');
316
+ }
317
+ onDragLeave() {
318
+ this.$dropzone.classList.remove('moj-multi-file-upload--dragover');
319
+ }
320
+
321
+ /**
322
+ * @param {DragEvent} event - Drag event
323
+ */
324
+ onDrop(event) {
325
+ event.preventDefault();
326
+ this.$dropzone.classList.remove('moj-multi-file-upload--dragover');
327
+ this.$feedbackContainer.classList.remove('moj-hidden');
328
+ this.$status.textContent = this.config.uploadStatusText;
329
+ this.uploadFiles(event.dataTransfer.files);
330
+ }
331
+
332
+ /**
333
+ * @param {FileList} files - File list
334
+ */
335
+ uploadFiles(files) {
336
+ for (const file of Array.from(files)) {
337
+ this.uploadFile(file);
338
+ }
339
+ }
340
+ onFileChange() {
341
+ this.$feedbackContainer.classList.remove('moj-hidden');
342
+ this.$status.textContent = this.config.uploadStatusText;
343
+ this.uploadFiles(this.$fileInput.files);
344
+ const $fileInput = this.$fileInput.cloneNode(true);
345
+ if (!$fileInput || !($fileInput instanceof HTMLInputElement)) {
346
+ return;
347
+ }
348
+ $fileInput.value = '';
349
+ this.$fileInput.replaceWith($fileInput);
350
+ this.setupFileInput();
351
+ this.$fileInput.focus();
352
+ }
353
+ onFileFocus() {
354
+ this.$label.classList.add('moj-multi-file-upload--focused');
355
+ }
356
+ onFileBlur() {
357
+ this.$label.classList.remove('moj-multi-file-upload--focused');
358
+ }
359
+
360
+ /**
361
+ * @param {UploadResponseSuccess['success']} success
362
+ */
363
+ getSuccessHtml(success) {
364
+ 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>`;
365
+ }
366
+
367
+ /**
368
+ * @param {UploadResponseError['error']} error
369
+ */
370
+ getErrorHtml(error) {
371
+ 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>`;
372
+ }
373
+
374
+ /**
375
+ * @param {File} file
376
+ */
377
+ getFileRow(file) {
378
+ const $row = document.createElement('div');
379
+ $row.classList.add('govuk-summary-list__row', 'moj-multi-file-upload__row');
380
+ $row.innerHTML = `
381
+ <div class="govuk-summary-list__value moj-multi-file-upload__message">
382
+ <span class="moj-multi-file-upload__filename">${file.name}</span>
383
+ <span class="moj-multi-file-upload__progress">0%</span>
384
+ </div>
385
+ <div class="govuk-summary-list__actions moj-multi-file-upload__actions"></div>
386
+ `;
387
+ return $row;
388
+ }
389
+
390
+ /**
391
+ * @param {UploadResponseFile} file
392
+ */
393
+ getDeleteButton(file) {
394
+ const $button = document.createElement('button');
395
+ $button.setAttribute('type', 'button');
396
+ $button.setAttribute('name', 'delete');
397
+ $button.setAttribute('value', file.filename);
398
+ $button.classList.add('moj-multi-file-upload__delete', 'govuk-button', 'govuk-button--secondary', 'govuk-!-margin-bottom-0');
399
+ $button.innerHTML = `Delete <span class="govuk-visually-hidden">${file.originalname}</span>`;
400
+ return $button;
401
+ }
402
+
403
+ /**
404
+ * @param {File} file
405
+ */
406
+ uploadFile(file) {
407
+ this.config.hooks.entryHook(this, file);
408
+ const $item = this.getFileRow(file);
409
+ const $message = $item.querySelector('.moj-multi-file-upload__message');
410
+ const $actions = $item.querySelector('.moj-multi-file-upload__actions');
411
+ const $progress = $item.querySelector('.moj-multi-file-upload__progress');
412
+ const formData = new FormData();
413
+ formData.append('documents', file);
414
+ this.$feedbackContainer.querySelector('.moj-multi-file-upload__list').append($item);
415
+ const xhr = new XMLHttpRequest();
416
+ const onLoad = () => {
417
+ if (xhr.status < 200 || xhr.status >= 300 || !('success' in xhr.response)) {
418
+ return onError();
419
+ }
420
+ $message.innerHTML = this.getSuccessHtml(xhr.response.success);
421
+ this.$status.textContent = xhr.response.success.messageText;
422
+ $actions.append(this.getDeleteButton(xhr.response.file));
423
+ this.config.hooks.exitHook(this, file, xhr, xhr.responseText);
424
+ };
425
+ const onError = () => {
426
+ const error = new Error(xhr.response && 'error' in xhr.response ? xhr.response.error.message : xhr.statusText || 'Upload failed');
427
+ $message.innerHTML = this.getErrorHtml(error);
428
+ this.$status.textContent = error.message;
429
+ this.config.hooks.errorHook(this, file, xhr, xhr.responseText, error);
430
+ };
431
+ xhr.addEventListener('load', onLoad);
432
+ xhr.addEventListener('error', onError);
433
+ xhr.upload.addEventListener('progress', event => {
434
+ if (!event.lengthComputable) {
435
+ return;
436
+ }
437
+ const percentComplete = Math.round(event.loaded / event.total * 100);
438
+ $progress.textContent = ` ${percentComplete}%`;
439
+ });
440
+ xhr.open('POST', this.config.uploadUrl);
441
+ xhr.responseType = 'json';
442
+ xhr.send(formData);
443
+ }
444
+
445
+ /**
446
+ * @param {MouseEvent} event - Click event
447
+ */
448
+ onFileDeleteClick(event) {
449
+ const $button = event.target;
450
+ if (!$button || !($button instanceof HTMLButtonElement) || !$button.classList.contains('moj-multi-file-upload__delete')) {
451
+ return;
452
+ }
453
+ event.preventDefault(); // if user refreshes page and then deletes
454
+
455
+ const xhr = new XMLHttpRequest();
456
+ xhr.addEventListener('load', () => {
457
+ if (xhr.status < 200 || xhr.status >= 300) {
458
+ return;
459
+ }
460
+ const $rows = Array.from(this.$feedbackContainer.querySelectorAll('.moj-multi-file-upload__row'));
461
+ if ($rows.length === 1) {
462
+ this.$feedbackContainer.classList.add('moj-hidden');
463
+ }
464
+ const $rowDelete = $rows.find($row => $row.contains($button));
465
+ if ($rowDelete) $rowDelete.remove();
466
+ this.config.hooks.deleteHook(this, undefined, xhr, xhr.responseText);
467
+ });
468
+ xhr.open('POST', this.config.deleteUrl);
469
+ xhr.setRequestHeader('Content-Type', 'application/json');
470
+ xhr.responseType = 'json';
471
+ xhr.send(JSON.stringify({
472
+ [$button.name]: $button.value
473
+ }));
474
+ }
475
+ static isSupported() {
476
+ return this.isDragAndDropSupported() && this.isFormDataSupported() && this.isFileApiSupported();
477
+ }
478
+ static isDragAndDropSupported() {
479
+ const div = document.createElement('div');
480
+ return typeof div.ondrop !== 'undefined';
481
+ }
482
+ static isFormDataSupported() {
483
+ return typeof FormData === 'function';
484
+ }
485
+ static isFileApiSupported() {
486
+ const input = document.createElement('input');
487
+ input.type = 'file';
488
+ return typeof input.files !== 'undefined';
489
+ }
490
+
491
+ /**
492
+ * Name for the component used when initialising using data-module attributes.
493
+ */
494
+ }
495
+
496
+ /**
497
+ * Multi file upload config
498
+ *
499
+ * @typedef {object} MultiFileUploadConfig
500
+ * @property {string} [uploadUrl] - File upload URL
501
+ * @property {string} [deleteUrl] - File delete URL
502
+ * @property {string} [uploadStatusText] - Upload status text
503
+ * @property {string} [dropzoneHintText] - Dropzone hint text
504
+ * @property {string} [dropzoneButtonText] - Dropzone button text
505
+ * @property {object} [feedbackContainer] - Feedback container config
506
+ * @property {string} [feedbackContainer.selector] - Selector for feedback container
507
+ * @property {Element | null} [feedbackContainer.element] - HTML element for feedback container
508
+ * @property {MultiFileUploadHooks} [hooks] - Upload hooks
509
+ */
510
+
511
+ /**
512
+ * Multi file upload hooks
513
+ *
514
+ * @typedef {object} MultiFileUploadHooks
515
+ * @property {OnUploadFileEntryHook} [entryHook] - File upload entry hook
516
+ * @property {OnUploadFileExitHook} [exitHook] - File upload exit hook
517
+ * @property {OnUploadFileErrorHook} [errorHook] - File upload error hook
518
+ * @property {OnUploadFileDeleteHook} [deleteHook] - File delete hook
519
+ */
520
+
521
+ /**
522
+ * Upload hook: File entry
523
+ *
524
+ * @callback OnUploadFileEntryHook
525
+ * @param {InstanceType<typeof MultiFileUpload>} upload - Multi file upload
526
+ * @param {File} file - File upload
527
+ */
528
+
529
+ /**
530
+ * Upload hook: File exit
531
+ *
532
+ * @callback OnUploadFileExitHook
533
+ * @param {InstanceType<typeof MultiFileUpload>} upload - Multi file upload
534
+ * @param {File} file - File upload
535
+ * @param {XMLHttpRequest} xhr - XMLHttpRequest
536
+ * @param {string} textStatus - Text status
537
+ */
538
+
539
+ /**
540
+ * Upload hook: File error
541
+ *
542
+ * @callback OnUploadFileErrorHook
543
+ * @param {InstanceType<typeof MultiFileUpload>} upload - Multi file upload
544
+ * @param {File} file - File upload
545
+ * @param {XMLHttpRequest} xhr - XMLHttpRequest
546
+ * @param {string} textStatus - Text status
547
+ * @param {Error} errorThrown - Error thrown
548
+ */
549
+
550
+ /**
551
+ * Upload hook: File delete
552
+ *
553
+ * @callback OnUploadFileDeleteHook
554
+ * @param {InstanceType<typeof MultiFileUpload>} upload - Multi file upload
555
+ * @param {File} [file] - File upload
556
+ * @param {XMLHttpRequest} xhr - XMLHttpRequest
557
+ * @param {string} textStatus - Text status
558
+ */
559
+
560
+ /**
561
+ * @typedef {object} UploadResponseSuccess
562
+ * @property {{ messageText: string, messageHtml: string }} success - Response success
563
+ * @property {UploadResponseFile} file - Response file
564
+ */
565
+
566
+ /**
567
+ * @typedef {object} UploadResponseError
568
+ * @property {{ message: string }} error - Response error
569
+ * @property {UploadResponseFile} file - Response file
570
+ */
571
+
572
+ /**
573
+ * @typedef {object} UploadResponseFile
574
+ * @property {string} filename - File name
575
+ * @property {string} originalname - Original file name
576
+ */
577
+
578
+ /**
579
+ * @import { Schema } from 'govuk-frontend/dist/govuk/common/configuration.mjs'
580
+ */
581
+ MultiFileUpload.moduleName = 'moj-multi-file-upload';
582
+ /**
583
+ * Multi file upload default config
584
+ *
585
+ * @type {MultiFileUploadConfig}
586
+ */
587
+ MultiFileUpload.defaults = Object.freeze({
588
+ uploadStatusText: 'Uploading files, please wait',
589
+ dropzoneHintText: 'Drag and drop files here or',
590
+ dropzoneButtonText: 'Choose files',
591
+ feedbackContainer: {
592
+ selector: '.moj-multi-file__uploaded-files'
593
+ },
594
+ hooks: {
595
+ entryHook: () => {},
596
+ exitHook: () => {},
597
+ errorHook: () => {},
598
+ deleteHook: () => {}
599
+ }
600
+ });
601
+ /**
602
+ * Multi file upload config schema
603
+ *
604
+ * @satisfies {Schema<MultiFileUploadConfig>}
605
+ */
606
+ MultiFileUpload.schema = Object.freeze(/** @type {const} */{
607
+ properties: {
608
+ uploadUrl: {
609
+ type: 'string'
610
+ },
611
+ deleteUrl: {
612
+ type: 'string'
613
+ },
614
+ uploadStatusText: {
615
+ type: 'string'
616
+ },
617
+ dropzoneHintText: {
618
+ type: 'string'
619
+ },
620
+ dropzoneButtonText: {
621
+ type: 'string'
622
+ },
623
+ feedbackContainer: {
624
+ type: 'object'
625
+ },
626
+ hooks: {
627
+ type: 'object'
628
+ }
629
+ }
630
+ });
631
+
632
+ export { MultiFileUpload };
633
+ //# sourceMappingURL=multi-file-upload.bundle.mjs.map