@ministryofjustice/frontend 3.3.0 → 3.4.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 (87) hide show
  1. package/README.md +4 -10
  2. package/govuk-prototype-kit.config.json +5 -16
  3. package/moj/all.jquery.min.js +77 -3
  4. package/moj/all.js +2022 -1444
  5. package/moj/all.scss +2 -0
  6. package/moj/all.spec.js +15 -13
  7. package/moj/components/_all.scss +1 -0
  8. package/moj/components/action-bar/_action-bar.scss +4 -6
  9. package/moj/components/add-another/_add-another.scss +9 -7
  10. package/moj/components/add-another/add-another.js +90 -69
  11. package/moj/components/add-another/add-another.spec.js +165 -0
  12. package/moj/components/alert/README.md +0 -0
  13. package/moj/components/alert/_alert.scss +142 -0
  14. package/moj/components/alert/alert.js +247 -0
  15. package/moj/components/alert/alert.spec.helper.js +67 -0
  16. package/moj/components/alert/alert.spec.js +229 -0
  17. package/moj/components/alert/macro.njk +3 -0
  18. package/moj/components/alert/template.njk +83 -0
  19. package/moj/components/badge/_badge.scss +3 -4
  20. package/moj/components/banner/_banner.scss +5 -10
  21. package/moj/components/button-menu/_button-menu.scss +10 -9
  22. package/moj/components/button-menu/button-menu.js +139 -136
  23. package/moj/components/button-menu/button-menu.spec.js +295 -296
  24. package/moj/components/cookie-banner/_cookie-banner.scss +6 -5
  25. package/moj/components/currency-input/_currency-input.scss +4 -4
  26. package/moj/components/date-picker/README.md +14 -17
  27. package/moj/components/date-picker/_date-picker.scss +122 -106
  28. package/moj/components/date-picker/date-picker.js +473 -471
  29. package/moj/components/date-picker/date-picker.spec.js +962 -914
  30. package/moj/components/filter/README.md +1 -1
  31. package/moj/components/filter/_filter.scss +53 -75
  32. package/moj/components/filter-toggle-button/filter-toggle-button.js +71 -67
  33. package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +203 -205
  34. package/moj/components/form-validator/form-validator.js +117 -109
  35. package/moj/components/header/_header.scss +17 -19
  36. package/moj/components/identity-bar/_identity-bar.scss +5 -5
  37. package/moj/components/interruption-card/_interruption-card.scss +9 -2
  38. package/moj/components/messages/_messages.scss +12 -19
  39. package/moj/components/multi-file-upload/README.md +1 -1
  40. package/moj/components/multi-file-upload/_multi-file-upload.scss +34 -30
  41. package/moj/components/multi-file-upload/multi-file-upload.js +188 -152
  42. package/moj/components/multi-file-upload/multi-file-upload.spec.js +510 -0
  43. package/moj/components/multi-select/_multi-select.scss +4 -3
  44. package/moj/components/multi-select/multi-select.js +55 -50
  45. package/moj/components/multi-select/multi-select.spec.js +128 -0
  46. package/moj/components/notification-badge/_notification-badge.scss +12 -12
  47. package/moj/components/organisation-switcher/_organisation-switcher.scss +1 -1
  48. package/moj/components/page-header-actions/_page-header-actions.scss +3 -2
  49. package/moj/components/pagination/_pagination.scss +26 -31
  50. package/moj/components/password-reveal/_password-reveal.scss +1 -2
  51. package/moj/components/password-reveal/password-reveal.js +22 -21
  52. package/moj/components/password-reveal/password-reveal.spec.js +39 -37
  53. package/moj/components/primary-navigation/_primary-navigation.scss +26 -29
  54. package/moj/components/progress-bar/_progress-bar.scss +21 -26
  55. package/moj/components/rich-text-editor/_rich-text-editor.scss +17 -16
  56. package/moj/components/rich-text-editor/rich-text-editor.js +117 -103
  57. package/moj/components/search/_search.scss +6 -4
  58. package/moj/components/search-toggle/search-toggle.js +29 -30
  59. package/moj/components/search-toggle/search-toggle.scss +21 -15
  60. package/moj/components/search-toggle/search-toggle.spec.js +129 -0
  61. package/moj/components/side-navigation/_side-navigation.scss +12 -21
  62. package/moj/components/sortable-table/_sortable-table.scss +25 -23
  63. package/moj/components/sortable-table/sortable-table.js +139 -117
  64. package/moj/components/sortable-table/sortable-table.spec.js +362 -0
  65. package/moj/components/sub-navigation/_sub-navigation.scss +24 -28
  66. package/moj/components/tag/_tag.scss +8 -9
  67. package/moj/components/task-list/_task-list.scss +8 -7
  68. package/moj/components/ticket-panel/_ticket-panel.scss +14 -6
  69. package/moj/components/timeline/_timeline.scss +18 -20
  70. package/moj/filters/all.js +28 -30
  71. package/moj/filters/prototype-kit-13-filters.js +2 -1
  72. package/moj/helpers/_all.scss +1 -0
  73. package/moj/helpers/_hidden.scss +1 -1
  74. package/moj/helpers/_links.scss +20 -0
  75. package/moj/helpers.js +160 -31
  76. package/moj/helpers.spec.js +235 -0
  77. package/moj/init.js +2 -2
  78. package/moj/moj-frontend.min.css +2 -2
  79. package/moj/moj-frontend.min.js +77 -3
  80. package/moj/namespace.js +2 -1
  81. package/moj/objects/_filter-layout.scss +11 -10
  82. package/moj/objects/_scrollable-pane.scss +11 -14
  83. package/moj/settings/_colours.scss +5 -0
  84. package/moj/settings/_measurements.scss +0 -2
  85. package/moj/utilities/_hidden.scss +3 -3
  86. package/moj/utilities/_width-container.scss +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1,510 @@
1
+ /* eslint-disable no-new */
2
+
3
+ const {
4
+ queryByRole,
5
+ getByLabelText,
6
+ fireEvent
7
+ } = require('@testing-library/dom')
8
+ const { userEvent } = require('@testing-library/user-event')
9
+ const { configureAxe } = require('jest-axe')
10
+ const sinon = require('sinon')
11
+
12
+ require('../../helpers.js')
13
+ require('./multi-file-upload.js')
14
+
15
+ const user = userEvent.setup()
16
+ const axe = configureAxe({
17
+ rules: {
18
+ // disable landmark rules when testing isolated components.
19
+ region: { enabled: false }
20
+ }
21
+ })
22
+
23
+ const createComponent = (options = {}) => {
24
+ const html = `
25
+ <div class="govuk-grid-row">
26
+ <div class="govuk-grid-column-two-thirds">
27
+ <div class="moj-multi-file-upload">
28
+ <div class="moj-multi-file__uploaded-files moj-hidden">
29
+ <h2 class="govuk-heading-m">Files added</h2>
30
+ <div class="govuk-summary-list moj-multi-file-upload__list">
31
+ </div>
32
+ </div>
33
+ <div class="moj-multi-file-upload__upload">
34
+ <div class="govuk-form-group">
35
+ <label class="govuk-label govuk-label--m" for="documents">
36
+ Upload a file
37
+ </label>
38
+ <input class="govuk-file-upload moj-multi-file-upload__input" id="documents" name="documents" type="file" multiple="">
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>`
44
+
45
+ document.body.insertAdjacentHTML('afterbegin', html)
46
+ const component = document.querySelector('.moj-multi-file-upload')
47
+ return {
48
+ component,
49
+ options: { container: component, ...options }
50
+ }
51
+ }
52
+
53
+ describe('Multi-file upload', () => {
54
+ let component
55
+ let options
56
+ let server
57
+ let uploadFileEntryHook
58
+ let uploadFileExitHook
59
+ let uploadFileErrorHook
60
+ let fileDeleteHook
61
+
62
+ beforeEach(() => {
63
+ server = sinon.fakeServerWithClock.create({
64
+ respondImmediately: true
65
+ })
66
+
67
+ uploadFileEntryHook = sinon.spy()
68
+ uploadFileExitHook = sinon.spy()
69
+ uploadFileErrorHook = sinon.spy()
70
+ fileDeleteHook = sinon.spy()
71
+ ;({ component, options } = createComponent({
72
+ uploadFileEntryHook,
73
+ uploadFileExitHook,
74
+ uploadFileErrorHook,
75
+ fileDeleteHook,
76
+ uploadUrl: '/upload',
77
+ deleteUrl: '/delete'
78
+ }))
79
+
80
+ new MOJFrontend.MultiFileUpload(options)
81
+ })
82
+
83
+ afterEach(() => {
84
+ document.body.innerHTML = ''
85
+ server.restore()
86
+ sinon.restore()
87
+ })
88
+
89
+ test('initialises with enhanced class', () => {
90
+ expect(component).toHaveClass('moj-multi-file-upload--enhanced')
91
+ })
92
+
93
+ test('creates dropzone with correct text', () => {
94
+ const dropzone = component.querySelector('.moj-multi-file-upload__dropzone')
95
+ expect(dropzone).toBeInTheDocument()
96
+ expect(dropzone).toHaveTextContent('Drag and drop files here or')
97
+ expect(dropzone.querySelector('label')).toHaveTextContent('Choose files')
98
+ })
99
+
100
+ test('creates status box for announcements', () => {
101
+ const statusBox = queryByRole(component, 'status')
102
+ expect(statusBox).toBeInTheDocument()
103
+ expect(statusBox).toHaveClass('govuk-visually-hidden')
104
+ })
105
+
106
+ describe('File upload handling', () => {
107
+ let file
108
+ let input
109
+ const successResponse = {
110
+ success: {
111
+ messageHtml: 'File uploaded successfully',
112
+ messageText: 'File uploaded successfully'
113
+ },
114
+ file: {
115
+ filename: 'test',
116
+ originalname: 'test.txt'
117
+ }
118
+ }
119
+
120
+ beforeEach(() => {
121
+ file = new File(['test content'], 'test.txt', { type: 'text/plain' })
122
+ input = component.querySelector('.moj-multi-file-upload__input')
123
+ input = getByLabelText(component, 'Upload a file')
124
+
125
+ // Configure server response for file upload
126
+ server.respondWith('POST', '/upload', [
127
+ 200,
128
+ { 'Content-Type': 'application/json' },
129
+ JSON.stringify(successResponse)
130
+ ])
131
+ })
132
+
133
+ test('handles file input change', async () => {
134
+ const changeEvent = new Event('change', { bubbles: true })
135
+
136
+ // input.files is not writable, so we do this to add the files to the input
137
+ Object.defineProperty(input, 'files', {
138
+ value: { files: [file] }
139
+ })
140
+
141
+ fireEvent(input, changeEvent)
142
+
143
+ const feedbackContainer = component.querySelector(
144
+ '.moj-multi-file__uploaded-files'
145
+ )
146
+ expect(feedbackContainer).not.toHaveClass('moj-hidden')
147
+ const newInput = getByLabelText(component, 'Upload a file')
148
+ expect(newInput).toHaveValue('')
149
+ expect(newInput).toHaveFocus()
150
+ })
151
+
152
+ test('displays upload progress', async () => {
153
+ // Create a spy on XMLHttpRequest to simulate upload progress
154
+ const xhr = sinon.useFakeXMLHttpRequest()
155
+ let request
156
+ xhr.onCreate = (req) => {
157
+ request = req
158
+ }
159
+
160
+ await user.upload(input, file)
161
+
162
+ request.uploadProgress({
163
+ lengthComputable: true,
164
+ loaded: 50,
165
+ total: 100
166
+ })
167
+
168
+ const fileRows = component.querySelectorAll('.moj-multi-file-upload__row')
169
+ const progressElement = component.querySelector(
170
+ '.moj-multi-file-upload__progress'
171
+ )
172
+ const nameElement = component.querySelector(
173
+ '.moj-multi-file-upload__filename'
174
+ )
175
+
176
+ expect(fileRows).toHaveLength(1)
177
+ expect(progressElement).toHaveTextContent('50%')
178
+ expect(nameElement).toHaveTextContent(file.name)
179
+
180
+ xhr.restore()
181
+ })
182
+
183
+ test('handles successful upload', async () => {
184
+ await user.upload(input, file)
185
+
186
+ expect(uploadFileEntryHook).toHaveBeenCalledOnce()
187
+ expect(uploadFileExitHook).toHaveBeenCalledOnce()
188
+ expect(uploadFileExitHook).toHaveBeenCalledAfter(uploadFileEntryHook)
189
+
190
+ const successMessage = component.querySelector(
191
+ '.moj-multi-file-upload__success'
192
+ )
193
+ const deleteButton = component.querySelector(
194
+ '.moj-multi-file-upload__delete'
195
+ )
196
+
197
+ expect(successMessage).toHaveTextContent('File uploaded successfully')
198
+ expect(deleteButton).toBeInTheDocument()
199
+ expect(deleteButton).toHaveAccessibleName(`Delete test.txt`)
200
+ expect(deleteButton).toHaveValue('test')
201
+ })
202
+
203
+ // eslint-disable-next-line jest/no-disabled-tests -- this fails as the component still attempts to access response.file (line 149)
204
+ test.skip('handles 200 status with error in response json', async () => {
205
+ server.respondWith('POST', '/upload', [
206
+ 200,
207
+ { 'Content-Type': 'application/json' },
208
+ JSON.stringify({
209
+ error: {
210
+ message: 'Upload failed'
211
+ }
212
+ })
213
+ ])
214
+
215
+ await user.upload(input, file)
216
+
217
+ const errorMessage = component.querySelector(
218
+ '.moj-multi-file-upload__error'
219
+ )
220
+ expect(errorMessage).toHaveTextContent('Upload failed')
221
+ })
222
+
223
+ test('handles non 200 response status', async () => {
224
+ server.respondWith('POST', '/upload', [
225
+ 500,
226
+ { 'Content-Type': 'text/plain' },
227
+ ''
228
+ ])
229
+
230
+ await user.upload(input, file)
231
+
232
+ expect(uploadFileErrorHook).toHaveBeenCalledOnce()
233
+ })
234
+ })
235
+
236
+ describe('File deletion', () => {
237
+ beforeEach(async () => {
238
+ const file = new File(['test content'], 'test.txt', {
239
+ type: 'text/plain'
240
+ })
241
+ const input = component.querySelector('.moj-multi-file-upload__input')
242
+
243
+ server.respondWith('POST', '/upload', [
244
+ 200,
245
+ { 'Content-Type': 'application/json' },
246
+ JSON.stringify({
247
+ success: {
248
+ messageHtml: 'File uploaded successfully'
249
+ },
250
+ file: {
251
+ filename: '123',
252
+ originalname: 'test.txt'
253
+ }
254
+ })
255
+ ])
256
+
257
+ await user.upload(input, file)
258
+ })
259
+
260
+ test('handles file deletion', async () => {
261
+ server.respondWith('POST', '/delete', [
262
+ 200,
263
+ { 'Content-Type': 'application/json' },
264
+ JSON.stringify({ success: true })
265
+ ])
266
+
267
+ const deleteButton = component.querySelector(
268
+ '.moj-multi-file-upload__delete'
269
+ )
270
+ await user.click(deleteButton)
271
+
272
+ expect(fileDeleteHook).toHaveBeenCalledOnce()
273
+ expect(server.requests[server.requests.length - 1].url).toBe('/delete')
274
+ expect(server.requests[server.requests.length - 1].method).toBe('POST')
275
+
276
+ const fileRow = component.querySelector('.moj-multi-file-upload__row')
277
+ expect(fileRow).not.toBeInTheDocument()
278
+ })
279
+
280
+ test('hides feedback container when all files are deleted', async () => {
281
+ server.respondWith('POST', '/delete', [
282
+ 200,
283
+ { 'Content-Type': 'application/json' },
284
+ JSON.stringify({ success: true })
285
+ ])
286
+
287
+ const deleteButton = component.querySelector(
288
+ '.moj-multi-file-upload__delete'
289
+ )
290
+ await user.click(deleteButton)
291
+
292
+ const feedbackContainer = component.querySelector(
293
+ '.moj-multi-file__uploaded-files'
294
+ )
295
+ expect(feedbackContainer).toHaveClass('moj-hidden')
296
+ })
297
+ })
298
+
299
+ describe('Drag and drop', () => {
300
+ test('handles dragover event', () => {
301
+ const dropzone = component.querySelector(
302
+ '.moj-multi-file-upload__dropzone'
303
+ )
304
+ const dragOverEvent = new Event('dragover')
305
+ dropzone.dispatchEvent(dragOverEvent)
306
+
307
+ expect(dropzone).toHaveClass('moj-multi-file-upload--dragover')
308
+ })
309
+
310
+ test('handles dragleave event', () => {
311
+ const dropzone = component.querySelector(
312
+ '.moj-multi-file-upload__dropzone'
313
+ )
314
+ dropzone.classList.add('moj-multi-file-upload--dragover')
315
+
316
+ const dragLeaveEvent = new Event('dragleave')
317
+ dropzone.dispatchEvent(dragLeaveEvent)
318
+
319
+ expect(dropzone).not.toHaveClass('moj-multi-file-upload--dragover')
320
+ })
321
+
322
+ test('handles file drop', () => {
323
+ server.respondWith('POST', '/upload', [
324
+ 200,
325
+ { 'Content-Type': 'application/json' },
326
+ JSON.stringify({
327
+ success: {
328
+ messageHtml: 'File uploaded successfully'
329
+ },
330
+ file: {
331
+ filename: 'test',
332
+ originalname: 'test.txt'
333
+ }
334
+ })
335
+ ])
336
+
337
+ const dropzone = component.querySelector(
338
+ '.moj-multi-file-upload__dropzone'
339
+ )
340
+ const file = new File(['test content'], 'test.txt', {
341
+ type: 'text/plain'
342
+ })
343
+
344
+ const dropEvent = new Event('drop')
345
+ dropEvent.preventDefault = () => {}
346
+ Object.defineProperty(dropEvent, 'dataTransfer', {
347
+ value: {
348
+ files: [file]
349
+ }
350
+ })
351
+
352
+ dropzone.dispatchEvent(dropEvent)
353
+
354
+ expect(server.requests).toHaveLength(1)
355
+ expect(server.requests[0].url).toBe('/upload')
356
+ expect(server.requests[0].method).toBe('POST')
357
+
358
+ const feedbackContainer = component.querySelector(
359
+ '.moj-multi-file__uploaded-files'
360
+ )
361
+ const successMessage = component.querySelector(
362
+ '.moj-multi-file-upload__success'
363
+ )
364
+ const deleteButton = component.querySelector(
365
+ '.moj-multi-file-upload__delete'
366
+ )
367
+
368
+ // test callbacks
369
+ expect(uploadFileEntryHook).toHaveBeenCalledOnce()
370
+ expect(uploadFileExitHook).toHaveBeenCalledOnce()
371
+ expect(uploadFileExitHook).toHaveBeenCalledAfter(uploadFileEntryHook)
372
+
373
+ // test file present in UI
374
+ expect(feedbackContainer).not.toHaveClass('moj-hidden')
375
+ expect(successMessage).toHaveTextContent('File uploaded successfully')
376
+ expect(deleteButton).toBeInTheDocument()
377
+ expect(deleteButton).toHaveAccessibleName(`Delete test.txt`)
378
+ expect(deleteButton).toHaveValue('test')
379
+ })
380
+ })
381
+
382
+ describe('Uploading multiple files', () => {
383
+ let files
384
+ let input
385
+ const successResponse = {
386
+ success: {
387
+ messageHtml: 'File uploaded successfully',
388
+ messageText: 'File uploaded successfully'
389
+ },
390
+ file: {
391
+ filename: 'test',
392
+ originalname: 'test.txt'
393
+ }
394
+ }
395
+
396
+ beforeEach(() => {
397
+ files = [
398
+ new File(['test content'], 'test-1.txt', { type: 'text/plain' }),
399
+ new File(['test content'], 'test-2.txt', { type: 'text/plain' })
400
+ ]
401
+ input = component.querySelector('.moj-multi-file-upload__input')
402
+ input = getByLabelText(component, 'Upload a file')
403
+
404
+ // Configure server response for file upload
405
+ server.respondWith('POST', '/upload', [
406
+ 200,
407
+ { 'Content-Type': 'application/json' },
408
+ JSON.stringify(successResponse)
409
+ ])
410
+ })
411
+
412
+ test('handles multiple files', async () => {
413
+ await user.upload(input, files)
414
+
415
+ const feedbackContainer = component.querySelector(
416
+ '.moj-multi-file__uploaded-files'
417
+ )
418
+ const fileRows = component.querySelectorAll('.moj-multi-file-upload__row')
419
+ const successMessages = component.querySelectorAll(
420
+ '.moj-multi-file-upload__success'
421
+ )
422
+ const deleteButtons = component.querySelectorAll(
423
+ '.moj-multi-file-upload__delete'
424
+ )
425
+
426
+ expect(uploadFileEntryHook).toHaveBeenCalledTwice()
427
+ expect(uploadFileExitHook).toHaveBeenCalledTwice()
428
+
429
+ expect(feedbackContainer).not.toHaveClass('moj-hidden')
430
+ expect(fileRows).toHaveLength(2)
431
+
432
+ expect(successMessages[0]).toHaveTextContent('File uploaded successfully')
433
+ expect(deleteButtons[0]).toHaveAccessibleName(`Delete test.txt`)
434
+ expect(deleteButtons[0]).toHaveValue('test')
435
+ expect(successMessages[1]).toHaveTextContent('File uploaded successfully')
436
+ expect(deleteButtons[1]).toHaveAccessibleName(`Delete test.txt`)
437
+ expect(deleteButtons[1]).toHaveValue('test')
438
+ })
439
+
440
+ test('handles multiple file drop', () => {
441
+ const dropzone = component.querySelector(
442
+ '.moj-multi-file-upload__dropzone'
443
+ )
444
+
445
+ const dropEvent = new Event('drop')
446
+ dropEvent.preventDefault = () => {}
447
+ Object.defineProperty(dropEvent, 'dataTransfer', {
448
+ value: {
449
+ files
450
+ }
451
+ })
452
+
453
+ dropzone.dispatchEvent(dropEvent)
454
+
455
+ expect(server.requests).toHaveLength(2)
456
+ expect(server.requests[0].url).toBe('/upload')
457
+ expect(server.requests[0].method).toBe('POST')
458
+
459
+ const feedbackContainer = component.querySelector(
460
+ '.moj-multi-file__uploaded-files'
461
+ )
462
+
463
+ const fileRows = component.querySelectorAll('.moj-multi-file-upload__row')
464
+ const successMessages = component.querySelectorAll(
465
+ '.moj-multi-file-upload__success'
466
+ )
467
+ const deleteButtons = component.querySelectorAll(
468
+ '.moj-multi-file-upload__delete'
469
+ )
470
+
471
+ expect(uploadFileEntryHook).toHaveBeenCalledTwice()
472
+ expect(uploadFileExitHook).toHaveBeenCalledTwice()
473
+
474
+ expect(feedbackContainer).not.toHaveClass('moj-hidden')
475
+ expect(fileRows).toHaveLength(2)
476
+
477
+ expect(successMessages[0]).toHaveTextContent('File uploaded successfully')
478
+ expect(deleteButtons[0]).toHaveAccessibleName(`Delete test.txt`)
479
+ expect(deleteButtons[0]).toHaveValue('test')
480
+ expect(successMessages[1]).toHaveTextContent('File uploaded successfully')
481
+ expect(deleteButtons[1]).toHaveAccessibleName(`Delete test.txt`)
482
+ expect(deleteButtons[1]).toHaveValue('test')
483
+ })
484
+ })
485
+
486
+ describe('Accessibility', () => {
487
+ let file
488
+ let input
489
+
490
+ beforeEach(() => {
491
+ file = new File(['test content'], 'test.txt', {
492
+ type: 'text/plain'
493
+ })
494
+ input = component.querySelector('.moj-multi-file-upload__input')
495
+ })
496
+
497
+ test('status messages are announced to screen readers', async () => {
498
+ await user.upload(input, file)
499
+
500
+ const statusBox = queryByRole(component, 'status')
501
+ expect(statusBox).toHaveTextContent('Uploading files, please wait')
502
+ })
503
+
504
+ test('component has no wcag violations', async () => {
505
+ expect(await axe(document.body)).toHaveNoViolations()
506
+ await user.upload(input, file)
507
+ expect(await axe(document.body)).toHaveNoViolations()
508
+ })
509
+ })
510
+ })
@@ -2,13 +2,14 @@
2
2
  # MULTI-SELECT
3
3
  ========================================================================== */
4
4
 
5
-
6
5
  .moj-multi-select__checkbox {
7
6
  display: inline-block;
8
7
  padding-left: 0;
9
8
  }
10
9
 
11
10
  .moj-multi-select__toggle-label {
12
- padding: 0 !important;
11
+ // stylelint-disable-next-line declaration-no-important
13
12
  margin: 0 !important;
14
- }
13
+ // stylelint-disable-next-line declaration-no-important
14
+ padding: 0 !important;
15
+ }
@@ -1,70 +1,75 @@
1
- MOJFrontend.MultiSelect = function(options) {
2
- this.container = $(options.container);
1
+ MOJFrontend.MultiSelect = function (options) {
2
+ this.container = $(options.container)
3
3
 
4
4
  if (this.container.data('moj-multi-select-initialised')) {
5
5
  return
6
6
  }
7
7
 
8
- this.container.data('moj-multi-select-initialised', true);
8
+ this.container.data('moj-multi-select-initialised', true)
9
9
 
10
- const idPrefix = options.id_prefix;
11
- let allId = 'checkboxes-all';
10
+ const idPrefix = options.id_prefix
11
+ let allId = 'checkboxes-all'
12
12
  if (typeof idPrefix !== 'undefined') {
13
- allId = idPrefix + 'checkboxes-all';
13
+ allId = `${idPrefix}checkboxes-all`
14
14
  }
15
15
 
16
- this.toggle = $(this.getToggleHtml(allId));
17
- this.toggleButton = this.toggle.find('input');
18
- this.toggleButton.on('click', $.proxy(this, 'onButtonClick'));
19
- this.container.append(this.toggle);
20
- this.checkboxes = $(options.checkboxes);
21
- this.checkboxes.on('click', $.proxy(this, 'onCheckboxClick'));
22
- this.checked = options.checked || false;
23
- };
16
+ this.toggle = $(this.getToggleHtml(allId))
17
+ this.toggleButton = this.toggle.find('input')
18
+ this.toggleButton.on('click', $.proxy(this, 'onButtonClick'))
19
+ this.container.append(this.toggle)
20
+ this.checkboxes = $(options.checkboxes)
21
+ this.checkboxes.on('click', $.proxy(this, 'onCheckboxClick'))
22
+ this.checked = options.checked || false
23
+ }
24
24
 
25
25
  MOJFrontend.MultiSelect.prototype.getToggleHtml = function (allId) {
26
- let html = '';
27
- html += '<div class="govuk-checkboxes__item govuk-checkboxes--small moj-multi-select__checkbox">';
28
- html += ` <input type="checkbox" class="govuk-checkboxes__input" id="${allId}">`;
29
- html += ` <label class="govuk-label govuk-checkboxes__label moj-multi-select__toggle-label" for="${allId}">`;
30
- html += ' <span class="govuk-visually-hidden">Select all</span>';
31
- html += ' </label>';
32
- html += '</div>';
33
- return html;
34
- };
26
+ let html = ''
27
+ html +=
28
+ '<div class="govuk-checkboxes__item govuk-checkboxes--small moj-multi-select__checkbox">'
29
+ html += ` <input type="checkbox" class="govuk-checkboxes__input" id="${allId}">`
30
+ html += ` <label class="govuk-label govuk-checkboxes__label moj-multi-select__toggle-label" for="${allId}">`
31
+ html += ' <span class="govuk-visually-hidden">Select all</span>'
32
+ html += ' </label>'
33
+ html += '</div>'
34
+ return html
35
+ }
35
36
 
36
- MOJFrontend.MultiSelect.prototype.onButtonClick = function(e) {
37
- if(this.checked) {
38
- this.uncheckAll();
39
- this.toggleButton[0].checked = false;
37
+ MOJFrontend.MultiSelect.prototype.onButtonClick = function (e) {
38
+ if (this.checked) {
39
+ this.uncheckAll()
40
+ this.toggleButton[0].checked = false
40
41
  } else {
41
- this.checkAll();
42
- this.toggleButton[0].checked = true;
42
+ this.checkAll()
43
+ this.toggleButton[0].checked = true
43
44
  }
44
- };
45
+ }
45
46
 
46
- MOJFrontend.MultiSelect.prototype.checkAll = function() {
47
- this.checkboxes.each($.proxy(function(index, el) {
48
- el.checked = true;
49
- }, this));
50
- this.checked = true;
51
- };
47
+ MOJFrontend.MultiSelect.prototype.checkAll = function () {
48
+ this.checkboxes.each(
49
+ $.proxy(function (index, el) {
50
+ el.checked = true
51
+ }, this)
52
+ )
53
+ this.checked = true
54
+ }
52
55
 
53
- MOJFrontend.MultiSelect.prototype.uncheckAll = function() {
54
- this.checkboxes.each($.proxy(function(index, el) {
55
- el.checked = false;
56
- }, this));
57
- this.checked = false;
58
- };
56
+ MOJFrontend.MultiSelect.prototype.uncheckAll = function () {
57
+ this.checkboxes.each(
58
+ $.proxy(function (index, el) {
59
+ el.checked = false
60
+ }, this)
61
+ )
62
+ this.checked = false
63
+ }
59
64
 
60
- MOJFrontend.MultiSelect.prototype.onCheckboxClick = function(e) {
61
- if(!e.target.checked) {
62
- this.toggleButton[0].checked = false;
63
- this.checked = false;
65
+ MOJFrontend.MultiSelect.prototype.onCheckboxClick = function (e) {
66
+ if (!e.target.checked) {
67
+ this.toggleButton[0].checked = false
68
+ this.checked = false
64
69
  } else {
65
- if(this.checkboxes.filter(':checked').length === this.checkboxes.length) {
66
- this.toggleButton[0].checked = true;
67
- this.checked = true;
70
+ if (this.checkboxes.filter(':checked').length === this.checkboxes.length) {
71
+ this.toggleButton[0].checked = true
72
+ this.checked = true
68
73
  }
69
74
  }
70
- };
75
+ }