katalyst-govuk-formbuilder 1.9.3 → 1.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1253 @@
1
+ function normaliseString(value, property) {
2
+ const trimmedValue = value ? value.trim() : '';
3
+ let output;
4
+ let outputType = property == null ? void 0 : property.type;
5
+ if (!outputType) {
6
+ if (['true', 'false'].includes(trimmedValue)) {
7
+ outputType = 'boolean';
8
+ }
9
+ if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) {
10
+ outputType = 'number';
11
+ }
12
+ }
13
+ switch (outputType) {
14
+ case 'boolean':
15
+ output = trimmedValue === 'true';
16
+ break;
17
+ case 'number':
18
+ output = Number(trimmedValue);
19
+ break;
20
+ default:
21
+ output = value;
22
+ }
23
+ return output;
24
+ }
25
+
26
+ function mergeConfigs(...configObjects) {
27
+ const formattedConfigObject = {};
28
+ for (const configObject of configObjects) {
29
+ for (const key of Object.keys(configObject)) {
30
+ const option = formattedConfigObject[key];
31
+ const override = configObject[key];
32
+ if (isObject(option) && isObject(override)) {
33
+ formattedConfigObject[key] = mergeConfigs(option, override);
34
+ } else {
35
+ formattedConfigObject[key] = override;
36
+ }
37
+ }
38
+ }
39
+ return formattedConfigObject;
40
+ }
41
+ function extractConfigByNamespace(Component, dataset, namespace) {
42
+ const property = Component.schema.properties[namespace];
43
+ if ((property == null ? void 0 : property.type) !== 'object') {
44
+ return;
45
+ }
46
+ const newObject = {
47
+ [namespace]: ({})
48
+ };
49
+ for (const [key, value] of Object.entries(dataset)) {
50
+ let current = newObject;
51
+ const keyParts = key.split('.');
52
+ for (const [index, name] of keyParts.entries()) {
53
+ if (typeof current === 'object') {
54
+ if (index < keyParts.length - 1) {
55
+ if (!isObject(current[name])) {
56
+ current[name] = {};
57
+ }
58
+ current = current[name];
59
+ } else if (key !== namespace) {
60
+ current[name] = normaliseString(value);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return newObject[namespace];
66
+ }
67
+ function getFragmentFromUrl(url) {
68
+ if (!url.includes('#')) {
69
+ return undefined;
70
+ }
71
+ return url.split('#').pop();
72
+ }
73
+ function setFocus($element, options = {}) {
74
+ var _options$onBeforeFocu;
75
+ const isFocusable = $element.getAttribute('tabindex');
76
+ if (!isFocusable) {
77
+ $element.setAttribute('tabindex', '-1');
78
+ }
79
+ function onFocus() {
80
+ $element.addEventListener('blur', onBlur, {
81
+ once: true
82
+ });
83
+ }
84
+ function onBlur() {
85
+ var _options$onBlur;
86
+ (_options$onBlur = options.onBlur) == null || _options$onBlur.call($element);
87
+ if (!isFocusable) {
88
+ $element.removeAttribute('tabindex');
89
+ }
90
+ }
91
+ $element.addEventListener('focus', onFocus, {
92
+ once: true
93
+ });
94
+ (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
95
+ $element.focus();
96
+ }
97
+ function isSupported($scope = document.body) {
98
+ if (!$scope) {
99
+ return false;
100
+ }
101
+ return $scope.classList.contains('govuk-frontend-supported');
102
+ }
103
+ function validateConfig(schema, config) {
104
+ const validationErrors = [];
105
+ for (const [name, conditions] of Object.entries(schema)) {
106
+ const errors = [];
107
+ if (Array.isArray(conditions)) {
108
+ for (const {
109
+ required,
110
+ errorMessage
111
+ } of conditions) {
112
+ if (!required.every(key => !!config[key])) {
113
+ errors.push(errorMessage);
114
+ }
115
+ }
116
+ if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
117
+ validationErrors.push(...errors);
118
+ }
119
+ }
120
+ }
121
+ return validationErrors;
122
+ }
123
+ function isArray(option) {
124
+ return Array.isArray(option);
125
+ }
126
+ function isObject(option) {
127
+ return !!option && typeof option === 'object' && !isArray(option);
128
+ }
129
+
130
+ function normaliseDataset(Component, dataset) {
131
+ const out = {};
132
+ for (const [field, property] of Object.entries(Component.schema.properties)) {
133
+ if (field in dataset) {
134
+ out[field] = normaliseString(dataset[field], property);
135
+ }
136
+ if ((property == null ? void 0 : property.type) === 'object') {
137
+ out[field] = extractConfigByNamespace(Component, dataset, field);
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+
143
+ class GOVUKFrontendError extends Error {
144
+ constructor(...args) {
145
+ super(...args);
146
+ this.name = 'GOVUKFrontendError';
147
+ }
148
+ }
149
+ class SupportError extends GOVUKFrontendError {
150
+ /**
151
+ * Checks if GOV.UK Frontend is supported on this page
152
+ *
153
+ * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
154
+ */
155
+ constructor($scope = document.body) {
156
+ 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';
157
+ super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
158
+ this.name = 'SupportError';
159
+ }
160
+ }
161
+ class ConfigError extends GOVUKFrontendError {
162
+ constructor(...args) {
163
+ super(...args);
164
+ this.name = 'ConfigError';
165
+ }
166
+ }
167
+ class ElementError extends GOVUKFrontendError {
168
+ constructor(messageOrOptions) {
169
+ let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
170
+ if (typeof messageOrOptions === 'object') {
171
+ const {
172
+ componentName,
173
+ identifier,
174
+ element,
175
+ expectedType
176
+ } = messageOrOptions;
177
+ message = `${componentName}: ${identifier}`;
178
+ message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
179
+ }
180
+ super(message);
181
+ this.name = 'ElementError';
182
+ }
183
+ }
184
+
185
+ class GOVUKFrontendComponent {
186
+ constructor() {
187
+ this.checkSupport();
188
+ }
189
+ checkSupport() {
190
+ if (!isSupported()) {
191
+ throw new SupportError();
192
+ }
193
+ }
194
+ }
195
+
196
+ class I18n {
197
+ constructor(translations = {}, config = {}) {
198
+ var _config$locale;
199
+ this.translations = void 0;
200
+ this.locale = void 0;
201
+ this.translations = translations;
202
+ this.locale = (_config$locale = config.locale) != null ? _config$locale : document.documentElement.lang || 'en';
203
+ }
204
+ t(lookupKey, options) {
205
+ if (!lookupKey) {
206
+ throw new Error('i18n: lookup key missing');
207
+ }
208
+ let translation = this.translations[lookupKey];
209
+ if (typeof (options == null ? void 0 : options.count) === 'number' && typeof translation === 'object') {
210
+ const translationPluralForm = translation[this.getPluralSuffix(lookupKey, options.count)];
211
+ if (translationPluralForm) {
212
+ translation = translationPluralForm;
213
+ }
214
+ }
215
+ if (typeof translation === 'string') {
216
+ if (translation.match(/%{(.\S+)}/)) {
217
+ if (!options) {
218
+ throw new Error('i18n: cannot replace placeholders in string if no option data provided');
219
+ }
220
+ return this.replacePlaceholders(translation, options);
221
+ }
222
+ return translation;
223
+ }
224
+ return lookupKey;
225
+ }
226
+ replacePlaceholders(translationString, options) {
227
+ const formatter = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : undefined;
228
+ return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
229
+ if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
230
+ const placeholderValue = options[placeholderKey];
231
+ if (placeholderValue === false || typeof placeholderValue !== 'number' && typeof placeholderValue !== 'string') {
232
+ return '';
233
+ }
234
+ if (typeof placeholderValue === 'number') {
235
+ return formatter ? formatter.format(placeholderValue) : `${placeholderValue}`;
236
+ }
237
+ return placeholderValue;
238
+ }
239
+ throw new Error(`i18n: no data found to replace ${placeholderWithBraces} placeholder in string`);
240
+ });
241
+ }
242
+ hasIntlPluralRulesSupport() {
243
+ return Boolean('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length);
244
+ }
245
+ getPluralSuffix(lookupKey, count) {
246
+ count = Number(count);
247
+ if (!isFinite(count)) {
248
+ return 'other';
249
+ }
250
+ const translation = this.translations[lookupKey];
251
+ const preferredForm = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(count) : this.selectPluralFormUsingFallbackRules(count);
252
+ if (typeof translation === 'object') {
253
+ if (preferredForm in translation) {
254
+ return preferredForm;
255
+ } else if ('other' in translation) {
256
+ console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`);
257
+ return 'other';
258
+ }
259
+ }
260
+ throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`);
261
+ }
262
+ selectPluralFormUsingFallbackRules(count) {
263
+ count = Math.abs(Math.floor(count));
264
+ const ruleset = this.getPluralRulesForLocale();
265
+ if (ruleset) {
266
+ return I18n.pluralRules[ruleset](count);
267
+ }
268
+ return 'other';
269
+ }
270
+ getPluralRulesForLocale() {
271
+ const localeShort = this.locale.split('-')[0];
272
+ for (const pluralRule in I18n.pluralRulesMap) {
273
+ const languages = I18n.pluralRulesMap[pluralRule];
274
+ if (languages.includes(this.locale) || languages.includes(localeShort)) {
275
+ return pluralRule;
276
+ }
277
+ }
278
+ }
279
+ }
280
+ I18n.pluralRulesMap = {
281
+ arabic: ['ar'],
282
+ chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
283
+ french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
284
+ german: ['af', 'sq', 'az', 'eu', 'bg', 'ca', 'da', 'nl', 'en', 'et', 'fi', 'ka', 'de', 'el', 'hu', 'lb', 'no', 'so', 'sw', 'sv', 'ta', 'te', 'tr', 'ur'],
285
+ irish: ['ga'],
286
+ russian: ['ru', 'uk'],
287
+ scottish: ['gd'],
288
+ spanish: ['pt-PT', 'it', 'es'],
289
+ welsh: ['cy']
290
+ };
291
+ I18n.pluralRules = {
292
+ arabic(n) {
293
+ if (n === 0) {
294
+ return 'zero';
295
+ }
296
+ if (n === 1) {
297
+ return 'one';
298
+ }
299
+ if (n === 2) {
300
+ return 'two';
301
+ }
302
+ if (n % 100 >= 3 && n % 100 <= 10) {
303
+ return 'few';
304
+ }
305
+ if (n % 100 >= 11 && n % 100 <= 99) {
306
+ return 'many';
307
+ }
308
+ return 'other';
309
+ },
310
+ chinese() {
311
+ return 'other';
312
+ },
313
+ french(n) {
314
+ return n === 0 || n === 1 ? 'one' : 'other';
315
+ },
316
+ german(n) {
317
+ return n === 1 ? 'one' : 'other';
318
+ },
319
+ irish(n) {
320
+ if (n === 1) {
321
+ return 'one';
322
+ }
323
+ if (n === 2) {
324
+ return 'two';
325
+ }
326
+ if (n >= 3 && n <= 6) {
327
+ return 'few';
328
+ }
329
+ if (n >= 7 && n <= 10) {
330
+ return 'many';
331
+ }
332
+ return 'other';
333
+ },
334
+ russian(n) {
335
+ const lastTwo = n % 100;
336
+ const last = lastTwo % 10;
337
+ if (last === 1 && lastTwo !== 11) {
338
+ return 'one';
339
+ }
340
+ if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) {
341
+ return 'few';
342
+ }
343
+ if (last === 0 || last >= 5 && last <= 9 || lastTwo >= 11 && lastTwo <= 14) {
344
+ return 'many';
345
+ }
346
+ return 'other';
347
+ },
348
+ scottish(n) {
349
+ if (n === 1 || n === 11) {
350
+ return 'one';
351
+ }
352
+ if (n === 2 || n === 12) {
353
+ return 'two';
354
+ }
355
+ if (n >= 3 && n <= 10 || n >= 13 && n <= 19) {
356
+ return 'few';
357
+ }
358
+ return 'other';
359
+ },
360
+ spanish(n) {
361
+ if (n === 1) {
362
+ return 'one';
363
+ }
364
+ if (n % 1000000 === 0 && n !== 0) {
365
+ return 'many';
366
+ }
367
+ return 'other';
368
+ },
369
+ welsh(n) {
370
+ if (n === 0) {
371
+ return 'zero';
372
+ }
373
+ if (n === 1) {
374
+ return 'one';
375
+ }
376
+ if (n === 2) {
377
+ return 'two';
378
+ }
379
+ if (n === 3) {
380
+ return 'few';
381
+ }
382
+ if (n === 6) {
383
+ return 'many';
384
+ }
385
+ return 'other';
386
+ }
387
+ };
388
+
389
+ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
390
+
391
+ /**
392
+ * JavaScript enhancements for the Button component
393
+ *
394
+ * @preserve
395
+ */
396
+ class Button extends GOVUKFrontendComponent {
397
+ /**
398
+ * @param {Element | null} $module - HTML element to use for button
399
+ * @param {ButtonConfig} [config] - Button config
400
+ */
401
+ constructor($module, config = {}) {
402
+ super();
403
+ this.$module = void 0;
404
+ this.config = void 0;
405
+ this.debounceFormSubmitTimer = null;
406
+ if (!($module instanceof HTMLElement)) {
407
+ throw new ElementError({
408
+ componentName: 'Button',
409
+ element: $module,
410
+ identifier: 'Root element (`$module`)'
411
+ });
412
+ }
413
+ this.$module = $module;
414
+ this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset));
415
+ this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
416
+ this.$module.addEventListener('click', event => this.debounce(event));
417
+ }
418
+ handleKeyDown(event) {
419
+ const $target = event.target;
420
+ if (event.key !== ' ') {
421
+ return;
422
+ }
423
+ if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') {
424
+ event.preventDefault();
425
+ $target.click();
426
+ }
427
+ }
428
+ debounce(event) {
429
+ if (!this.config.preventDoubleClick) {
430
+ return;
431
+ }
432
+ if (this.debounceFormSubmitTimer) {
433
+ event.preventDefault();
434
+ return false;
435
+ }
436
+ this.debounceFormSubmitTimer = window.setTimeout(() => {
437
+ this.debounceFormSubmitTimer = null;
438
+ }, DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Button config
444
+ *
445
+ * @typedef {object} ButtonConfig
446
+ * @property {boolean} [preventDoubleClick=false] - Prevent accidental double
447
+ * clicks on submit buttons from submitting forms multiple times.
448
+ */
449
+
450
+ /**
451
+ * @typedef {import('../../common/index.mjs').Schema} Schema
452
+ */
453
+ Button.moduleName = 'govuk-button';
454
+ Button.defaults = Object.freeze({
455
+ preventDoubleClick: false
456
+ });
457
+ Button.schema = Object.freeze({
458
+ properties: {
459
+ preventDoubleClick: {
460
+ type: 'boolean'
461
+ }
462
+ }
463
+ });
464
+
465
+ function closestAttributeValue($element, attributeName) {
466
+ const $closestElementWithAttribute = $element.closest(`[${attributeName}]`);
467
+ return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
468
+ }
469
+
470
+ /**
471
+ * Character count component
472
+ *
473
+ * Tracks the number of characters or words in the `.govuk-js-character-count`
474
+ * `<textarea>` inside the element. Displays a message with the remaining number
475
+ * of characters/words available, or the number of characters/words in excess.
476
+ *
477
+ * You can configure the message to only appear after a certain percentage
478
+ * of the available characters/words has been entered.
479
+ *
480
+ * @preserve
481
+ */
482
+ class CharacterCount extends GOVUKFrontendComponent {
483
+ /**
484
+ * @param {Element | null} $module - HTML element to use for character count
485
+ * @param {CharacterCountConfig} [config] - Character count config
486
+ */
487
+ constructor($module, config = {}) {
488
+ var _ref, _this$config$maxwords;
489
+ super();
490
+ this.$module = void 0;
491
+ this.$textarea = void 0;
492
+ this.$visibleCountMessage = void 0;
493
+ this.$screenReaderCountMessage = void 0;
494
+ this.lastInputTimestamp = null;
495
+ this.lastInputValue = '';
496
+ this.valueChecker = null;
497
+ this.config = void 0;
498
+ this.i18n = void 0;
499
+ this.maxLength = void 0;
500
+ if (!($module instanceof HTMLElement)) {
501
+ throw new ElementError({
502
+ componentName: 'Character count',
503
+ element: $module,
504
+ identifier: 'Root element (`$module`)'
505
+ });
506
+ }
507
+ const $textarea = $module.querySelector('.govuk-js-character-count');
508
+ if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
509
+ throw new ElementError({
510
+ componentName: 'Character count',
511
+ element: $textarea,
512
+ expectedType: 'HTMLTextareaElement or HTMLInputElement',
513
+ identifier: 'Form field (`.govuk-js-character-count`)'
514
+ });
515
+ }
516
+ const datasetConfig = normaliseDataset(CharacterCount, $module.dataset);
517
+ let configOverrides = {};
518
+ if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
519
+ configOverrides = {
520
+ maxlength: undefined,
521
+ maxwords: undefined
522
+ };
523
+ }
524
+ this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
525
+ const errors = validateConfig(CharacterCount.schema, this.config);
526
+ if (errors[0]) {
527
+ throw new ConfigError(`Character count: ${errors[0]}`);
528
+ }
529
+ this.i18n = new I18n(this.config.i18n, {
530
+ locale: closestAttributeValue($module, 'lang')
531
+ });
532
+ this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
533
+ this.$module = $module;
534
+ this.$textarea = $textarea;
535
+ const textareaDescriptionId = `${this.$textarea.id}-info`;
536
+ const $textareaDescription = document.getElementById(textareaDescriptionId);
537
+ if (!$textareaDescription) {
538
+ throw new ElementError({
539
+ componentName: 'Character count',
540
+ element: $textareaDescription,
541
+ identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
542
+ });
543
+ }
544
+ if (`${$textareaDescription.textContent}`.match(/^\s*$/)) {
545
+ $textareaDescription.textContent = this.i18n.t('textareaDescription', {
546
+ count: this.maxLength
547
+ });
548
+ }
549
+ this.$textarea.insertAdjacentElement('afterend', $textareaDescription);
550
+ const $screenReaderCountMessage = document.createElement('div');
551
+ $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
552
+ $screenReaderCountMessage.setAttribute('aria-live', 'polite');
553
+ this.$screenReaderCountMessage = $screenReaderCountMessage;
554
+ $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
555
+ const $visibleCountMessage = document.createElement('div');
556
+ $visibleCountMessage.className = $textareaDescription.className;
557
+ $visibleCountMessage.classList.add('govuk-character-count__status');
558
+ $visibleCountMessage.setAttribute('aria-hidden', 'true');
559
+ this.$visibleCountMessage = $visibleCountMessage;
560
+ $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
561
+ $textareaDescription.classList.add('govuk-visually-hidden');
562
+ this.$textarea.removeAttribute('maxlength');
563
+ this.bindChangeEvents();
564
+ window.addEventListener('pageshow', () => this.updateCountMessage());
565
+ this.updateCountMessage();
566
+ }
567
+ bindChangeEvents() {
568
+ this.$textarea.addEventListener('keyup', () => this.handleKeyUp());
569
+ this.$textarea.addEventListener('focus', () => this.handleFocus());
570
+ this.$textarea.addEventListener('blur', () => this.handleBlur());
571
+ }
572
+ handleKeyUp() {
573
+ this.updateVisibleCountMessage();
574
+ this.lastInputTimestamp = Date.now();
575
+ }
576
+ handleFocus() {
577
+ this.valueChecker = window.setInterval(() => {
578
+ if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
579
+ this.updateIfValueChanged();
580
+ }
581
+ }, 1000);
582
+ }
583
+ handleBlur() {
584
+ if (this.valueChecker) {
585
+ window.clearInterval(this.valueChecker);
586
+ }
587
+ }
588
+ updateIfValueChanged() {
589
+ if (this.$textarea.value !== this.lastInputValue) {
590
+ this.lastInputValue = this.$textarea.value;
591
+ this.updateCountMessage();
592
+ }
593
+ }
594
+ updateCountMessage() {
595
+ this.updateVisibleCountMessage();
596
+ this.updateScreenReaderCountMessage();
597
+ }
598
+ updateVisibleCountMessage() {
599
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
600
+ const isError = remainingNumber < 0;
601
+ this.$visibleCountMessage.classList.toggle('govuk-character-count__message--disabled', !this.isOverThreshold());
602
+ this.$textarea.classList.toggle('govuk-textarea--error', isError);
603
+ this.$visibleCountMessage.classList.toggle('govuk-error-message', isError);
604
+ this.$visibleCountMessage.classList.toggle('govuk-hint', !isError);
605
+ this.$visibleCountMessage.textContent = this.getCountMessage();
606
+ }
607
+ updateScreenReaderCountMessage() {
608
+ if (this.isOverThreshold()) {
609
+ this.$screenReaderCountMessage.removeAttribute('aria-hidden');
610
+ } else {
611
+ this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true');
612
+ }
613
+ this.$screenReaderCountMessage.textContent = this.getCountMessage();
614
+ }
615
+ count(text) {
616
+ if (this.config.maxwords) {
617
+ var _text$match;
618
+ const tokens = (_text$match = text.match(/\S+/g)) != null ? _text$match : [];
619
+ return tokens.length;
620
+ }
621
+ return text.length;
622
+ }
623
+ getCountMessage() {
624
+ const remainingNumber = this.maxLength - this.count(this.$textarea.value);
625
+ const countType = this.config.maxwords ? 'words' : 'characters';
626
+ return this.formatCountMessage(remainingNumber, countType);
627
+ }
628
+ formatCountMessage(remainingNumber, countType) {
629
+ if (remainingNumber === 0) {
630
+ return this.i18n.t(`${countType}AtLimit`);
631
+ }
632
+ const translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
633
+ return this.i18n.t(`${countType}${translationKeySuffix}`, {
634
+ count: Math.abs(remainingNumber)
635
+ });
636
+ }
637
+ isOverThreshold() {
638
+ if (!this.config.threshold) {
639
+ return true;
640
+ }
641
+ const currentLength = this.count(this.$textarea.value);
642
+ const maxLength = this.maxLength;
643
+ const thresholdValue = maxLength * this.config.threshold / 100;
644
+ return thresholdValue <= currentLength;
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Character count config
650
+ *
651
+ * @see {@link CharacterCount.defaults}
652
+ * @typedef {object} CharacterCountConfig
653
+ * @property {number} [maxlength] - The maximum number of characters.
654
+ * If maxwords is provided, the maxlength option will be ignored.
655
+ * @property {number} [maxwords] - The maximum number of words. If maxwords is
656
+ * provided, the maxlength option will be ignored.
657
+ * @property {number} [threshold=0] - The percentage value of the limit at
658
+ * which point the count message is displayed. If this attribute is set, the
659
+ * count message will be hidden by default.
660
+ * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations
661
+ */
662
+
663
+ /**
664
+ * Character count translations
665
+ *
666
+ * @see {@link CharacterCount.defaults.i18n}
667
+ * @typedef {object} CharacterCountTranslations
668
+ *
669
+ * Messages shown to users as they type. It provides feedback on how many words
670
+ * or characters they have remaining or if they are over the limit. This also
671
+ * includes a message used as an accessible description for the textarea.
672
+ * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
673
+ * when the number of characters is under the configured maximum, `maxlength`.
674
+ * This message is displayed visually and through assistive technologies. The
675
+ * component will replace the `%{count}` placeholder with the number of
676
+ * remaining characters. This is a [pluralised list of
677
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
678
+ * @property {string} [charactersAtLimit] - Message displayed when the number of
679
+ * characters reaches the configured maximum, `maxlength`. This message is
680
+ * displayed visually and through assistive technologies.
681
+ * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
682
+ * when the number of characters is over the configured maximum, `maxlength`.
683
+ * This message is displayed visually and through assistive technologies. The
684
+ * component will replace the `%{count}` placeholder with the number of
685
+ * remaining characters. This is a [pluralised list of
686
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
687
+ * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
688
+ * the number of words is under the configured maximum, `maxlength`. This
689
+ * message is displayed visually and through assistive technologies. The
690
+ * component will replace the `%{count}` placeholder with the number of
691
+ * remaining words. This is a [pluralised list of
692
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
693
+ * @property {string} [wordsAtLimit] - Message displayed when the number of
694
+ * words reaches the configured maximum, `maxlength`. This message is
695
+ * displayed visually and through assistive technologies.
696
+ * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
697
+ * the number of words is over the configured maximum, `maxlength`. This
698
+ * message is displayed visually and through assistive technologies. The
699
+ * component will replace the `%{count}` placeholder with the number of
700
+ * remaining words. This is a [pluralised list of
701
+ * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
702
+ * @property {TranslationPluralForms} [textareaDescription] - Message made
703
+ * available to assistive technologies, if none is already present in the
704
+ * HTML, to describe that the component accepts only a limited amount of
705
+ * content. It is visible on the page when JavaScript is unavailable. The
706
+ * component will replace the `%{count}` placeholder with the value of the
707
+ * `maxlength` or `maxwords` parameter.
708
+ */
709
+
710
+ /**
711
+ * @typedef {import('../../common/index.mjs').Schema} Schema
712
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
713
+ */
714
+ CharacterCount.moduleName = 'govuk-character-count';
715
+ CharacterCount.defaults = Object.freeze({
716
+ threshold: 0,
717
+ i18n: {
718
+ charactersUnderLimit: {
719
+ one: 'You have %{count} character remaining',
720
+ other: 'You have %{count} characters remaining'
721
+ },
722
+ charactersAtLimit: 'You have 0 characters remaining',
723
+ charactersOverLimit: {
724
+ one: 'You have %{count} character too many',
725
+ other: 'You have %{count} characters too many'
726
+ },
727
+ wordsUnderLimit: {
728
+ one: 'You have %{count} word remaining',
729
+ other: 'You have %{count} words remaining'
730
+ },
731
+ wordsAtLimit: 'You have 0 words remaining',
732
+ wordsOverLimit: {
733
+ one: 'You have %{count} word too many',
734
+ other: 'You have %{count} words too many'
735
+ },
736
+ textareaDescription: {
737
+ other: ''
738
+ }
739
+ }
740
+ });
741
+ CharacterCount.schema = Object.freeze({
742
+ properties: {
743
+ i18n: {
744
+ type: 'object'
745
+ },
746
+ maxwords: {
747
+ type: 'number'
748
+ },
749
+ maxlength: {
750
+ type: 'number'
751
+ },
752
+ threshold: {
753
+ type: 'number'
754
+ }
755
+ },
756
+ anyOf: [{
757
+ required: ['maxwords'],
758
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
759
+ }, {
760
+ required: ['maxlength'],
761
+ errorMessage: 'Either "maxlength" or "maxwords" must be provided'
762
+ }]
763
+ });
764
+
765
+ /**
766
+ * Checkboxes component
767
+ *
768
+ * @preserve
769
+ */
770
+ class Checkboxes extends GOVUKFrontendComponent {
771
+ /**
772
+ * Checkboxes can be associated with a 'conditionally revealed' content block
773
+ * – for example, a checkbox for 'Phone' could reveal an additional form field
774
+ * for the user to enter their phone number.
775
+ *
776
+ * These associations are made using a `data-aria-controls` attribute, which
777
+ * is promoted to an aria-controls attribute during initialisation.
778
+ *
779
+ * We also need to restore the state of any conditional reveals on the page
780
+ * (for example if the user has navigated back), and set up event handlers to
781
+ * keep the reveal in sync with the checkbox state.
782
+ *
783
+ * @param {Element | null} $module - HTML element to use for checkboxes
784
+ */
785
+ constructor($module) {
786
+ super();
787
+ this.$module = void 0;
788
+ this.$inputs = void 0;
789
+ if (!($module instanceof HTMLElement)) {
790
+ throw new ElementError({
791
+ componentName: 'Checkboxes',
792
+ element: $module,
793
+ identifier: 'Root element (`$module`)'
794
+ });
795
+ }
796
+ const $inputs = $module.querySelectorAll('input[type="checkbox"]');
797
+ if (!$inputs.length) {
798
+ throw new ElementError({
799
+ componentName: 'Checkboxes',
800
+ identifier: 'Form inputs (`<input type="checkbox">`)'
801
+ });
802
+ }
803
+ this.$module = $module;
804
+ this.$inputs = $inputs;
805
+ this.$inputs.forEach($input => {
806
+ const targetId = $input.getAttribute('data-aria-controls');
807
+ if (!targetId) {
808
+ return;
809
+ }
810
+ if (!document.getElementById(targetId)) {
811
+ throw new ElementError({
812
+ componentName: 'Checkboxes',
813
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
814
+ });
815
+ }
816
+ $input.setAttribute('aria-controls', targetId);
817
+ $input.removeAttribute('data-aria-controls');
818
+ });
819
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
820
+ this.syncAllConditionalReveals();
821
+ this.$module.addEventListener('click', event => this.handleClick(event));
822
+ }
823
+ syncAllConditionalReveals() {
824
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
825
+ }
826
+ syncConditionalRevealWithInputState($input) {
827
+ const targetId = $input.getAttribute('aria-controls');
828
+ if (!targetId) {
829
+ return;
830
+ }
831
+ const $target = document.getElementById(targetId);
832
+ if ($target != null && $target.classList.contains('govuk-checkboxes__conditional')) {
833
+ const inputIsChecked = $input.checked;
834
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
835
+ $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
836
+ }
837
+ }
838
+ unCheckAllInputsExcept($input) {
839
+ const allInputsWithSameName = document.querySelectorAll(`input[type="checkbox"][name="${$input.name}"]`);
840
+ allInputsWithSameName.forEach($inputWithSameName => {
841
+ const hasSameFormOwner = $input.form === $inputWithSameName.form;
842
+ if (hasSameFormOwner && $inputWithSameName !== $input) {
843
+ $inputWithSameName.checked = false;
844
+ this.syncConditionalRevealWithInputState($inputWithSameName);
845
+ }
846
+ });
847
+ }
848
+ unCheckExclusiveInputs($input) {
849
+ const allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(`input[data-behaviour="exclusive"][type="checkbox"][name="${$input.name}"]`);
850
+ allInputsWithSameNameAndExclusiveBehaviour.forEach($exclusiveInput => {
851
+ const hasSameFormOwner = $input.form === $exclusiveInput.form;
852
+ if (hasSameFormOwner) {
853
+ $exclusiveInput.checked = false;
854
+ this.syncConditionalRevealWithInputState($exclusiveInput);
855
+ }
856
+ });
857
+ }
858
+ handleClick(event) {
859
+ const $clickedInput = event.target;
860
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'checkbox') {
861
+ return;
862
+ }
863
+ const hasAriaControls = $clickedInput.getAttribute('aria-controls');
864
+ if (hasAriaControls) {
865
+ this.syncConditionalRevealWithInputState($clickedInput);
866
+ }
867
+ if (!$clickedInput.checked) {
868
+ return;
869
+ }
870
+ const hasBehaviourExclusive = $clickedInput.getAttribute('data-behaviour') === 'exclusive';
871
+ if (hasBehaviourExclusive) {
872
+ this.unCheckAllInputsExcept($clickedInput);
873
+ } else {
874
+ this.unCheckExclusiveInputs($clickedInput);
875
+ }
876
+ }
877
+ }
878
+ Checkboxes.moduleName = 'govuk-checkboxes';
879
+
880
+ /**
881
+ * Error summary component
882
+ *
883
+ * Takes focus on initialisation for accessible announcement, unless disabled in
884
+ * configuration.
885
+ *
886
+ * @preserve
887
+ */
888
+ class ErrorSummary extends GOVUKFrontendComponent {
889
+ /**
890
+ * @param {Element | null} $module - HTML element to use for error summary
891
+ * @param {ErrorSummaryConfig} [config] - Error summary config
892
+ */
893
+ constructor($module, config = {}) {
894
+ super();
895
+ this.$module = void 0;
896
+ this.config = void 0;
897
+ if (!($module instanceof HTMLElement)) {
898
+ throw new ElementError({
899
+ componentName: 'Error summary',
900
+ element: $module,
901
+ identifier: 'Root element (`$module`)'
902
+ });
903
+ }
904
+ this.$module = $module;
905
+ this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset));
906
+ if (!this.config.disableAutoFocus) {
907
+ setFocus(this.$module);
908
+ }
909
+ this.$module.addEventListener('click', event => this.handleClick(event));
910
+ }
911
+ handleClick(event) {
912
+ const $target = event.target;
913
+ if ($target && this.focusTarget($target)) {
914
+ event.preventDefault();
915
+ }
916
+ }
917
+ focusTarget($target) {
918
+ if (!($target instanceof HTMLAnchorElement)) {
919
+ return false;
920
+ }
921
+ const inputId = getFragmentFromUrl($target.href);
922
+ if (!inputId) {
923
+ return false;
924
+ }
925
+ const $input = document.getElementById(inputId);
926
+ if (!$input) {
927
+ return false;
928
+ }
929
+ const $legendOrLabel = this.getAssociatedLegendOrLabel($input);
930
+ if (!$legendOrLabel) {
931
+ return false;
932
+ }
933
+ $legendOrLabel.scrollIntoView();
934
+ $input.focus({
935
+ preventScroll: true
936
+ });
937
+ return true;
938
+ }
939
+ getAssociatedLegendOrLabel($input) {
940
+ var _document$querySelect;
941
+ const $fieldset = $input.closest('fieldset');
942
+ if ($fieldset) {
943
+ const $legends = $fieldset.getElementsByTagName('legend');
944
+ if ($legends.length) {
945
+ const $candidateLegend = $legends[0];
946
+ if ($input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio')) {
947
+ return $candidateLegend;
948
+ }
949
+ const legendTop = $candidateLegend.getBoundingClientRect().top;
950
+ const inputRect = $input.getBoundingClientRect();
951
+ if (inputRect.height && window.innerHeight) {
952
+ const inputBottom = inputRect.top + inputRect.height;
953
+ if (inputBottom - legendTop < window.innerHeight / 2) {
954
+ return $candidateLegend;
955
+ }
956
+ }
957
+ }
958
+ }
959
+ return (_document$querySelect = document.querySelector(`label[for='${$input.getAttribute('id')}']`)) != null ? _document$querySelect : $input.closest('label');
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Error summary config
965
+ *
966
+ * @typedef {object} ErrorSummaryConfig
967
+ * @property {boolean} [disableAutoFocus=false] - If set to `true` the error
968
+ * summary will not be focussed when the page loads.
969
+ */
970
+
971
+ /**
972
+ * @typedef {import('../../common/index.mjs').Schema} Schema
973
+ */
974
+ ErrorSummary.moduleName = 'govuk-error-summary';
975
+ ErrorSummary.defaults = Object.freeze({
976
+ disableAutoFocus: false
977
+ });
978
+ ErrorSummary.schema = Object.freeze({
979
+ properties: {
980
+ disableAutoFocus: {
981
+ type: 'boolean'
982
+ }
983
+ }
984
+ });
985
+
986
+ /**
987
+ * Password input component
988
+ *
989
+ * @preserve
990
+ */
991
+ class PasswordInput extends GOVUKFrontendComponent {
992
+ /**
993
+ * @param {Element | null} $module - HTML element to use for password input
994
+ * @param {PasswordInputConfig} [config] - Password input config
995
+ */
996
+ constructor($module, config = {}) {
997
+ super();
998
+ this.$module = void 0;
999
+ this.config = void 0;
1000
+ this.i18n = void 0;
1001
+ this.$input = void 0;
1002
+ this.$showHideButton = void 0;
1003
+ this.$screenReaderStatusMessage = void 0;
1004
+ if (!($module instanceof HTMLElement)) {
1005
+ throw new ElementError({
1006
+ componentName: 'Password input',
1007
+ element: $module,
1008
+ identifier: 'Root element (`$module`)'
1009
+ });
1010
+ }
1011
+ const $input = $module.querySelector('.govuk-js-password-input-input');
1012
+ if (!($input instanceof HTMLInputElement)) {
1013
+ throw new ElementError({
1014
+ componentName: 'Password input',
1015
+ element: $input,
1016
+ expectedType: 'HTMLInputElement',
1017
+ identifier: 'Form field (`.govuk-js-password-input-input`)'
1018
+ });
1019
+ }
1020
+ if ($input.type !== 'password') {
1021
+ throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1022
+ }
1023
+ const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle');
1024
+ if (!($showHideButton instanceof HTMLButtonElement)) {
1025
+ throw new ElementError({
1026
+ componentName: 'Password input',
1027
+ element: $showHideButton,
1028
+ expectedType: 'HTMLButtonElement',
1029
+ identifier: 'Button (`.govuk-js-password-input-toggle`)'
1030
+ });
1031
+ }
1032
+ if ($showHideButton.type !== 'button') {
1033
+ throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1034
+ }
1035
+ this.$module = $module;
1036
+ this.$input = $input;
1037
+ this.$showHideButton = $showHideButton;
1038
+ this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset));
1039
+ this.i18n = new I18n(this.config.i18n, {
1040
+ locale: closestAttributeValue($module, 'lang')
1041
+ });
1042
+ this.$showHideButton.removeAttribute('hidden');
1043
+ const $screenReaderStatusMessage = document.createElement('div');
1044
+ $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
1045
+ $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
1046
+ this.$screenReaderStatusMessage = $screenReaderStatusMessage;
1047
+ this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
1048
+ this.$showHideButton.addEventListener('click', this.toggle.bind(this));
1049
+ if (this.$input.form) {
1050
+ this.$input.form.addEventListener('submit', () => this.hide());
1051
+ }
1052
+ window.addEventListener('pageshow', event => {
1053
+ if (event.persisted && this.$input.type !== 'password') {
1054
+ this.hide();
1055
+ }
1056
+ });
1057
+ this.hide();
1058
+ }
1059
+ toggle(event) {
1060
+ event.preventDefault();
1061
+ if (this.$input.type === 'password') {
1062
+ this.show();
1063
+ return;
1064
+ }
1065
+ this.hide();
1066
+ }
1067
+ show() {
1068
+ this.setType('text');
1069
+ }
1070
+ hide() {
1071
+ this.setType('password');
1072
+ }
1073
+ setType(type) {
1074
+ if (type === this.$input.type) {
1075
+ return;
1076
+ }
1077
+ this.$input.setAttribute('type', type);
1078
+ const isHidden = type === 'password';
1079
+ const prefixButton = isHidden ? 'show' : 'hide';
1080
+ const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown';
1081
+ this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`);
1082
+ this.$showHideButton.setAttribute('aria-label', this.i18n.t(`${prefixButton}PasswordAriaLabel`));
1083
+ this.$screenReaderStatusMessage.innerText = this.i18n.t(`${prefixStatus}Announcement`);
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * Password input config
1089
+ *
1090
+ * @typedef {object} PasswordInputConfig
1091
+ * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations
1092
+ */
1093
+
1094
+ /**
1095
+ * Password input translations
1096
+ *
1097
+ * @see {@link PasswordInput.defaults.i18n}
1098
+ * @typedef {object} PasswordInputTranslations
1099
+ *
1100
+ * Messages displayed to the user indicating the state of the show/hide toggle.
1101
+ * @property {string} [showPassword] - Visible text of the button when the
1102
+ * password is currently hidden. Plain text only.
1103
+ * @property {string} [hidePassword] - Visible text of the button when the
1104
+ * password is currently visible. Plain text only.
1105
+ * @property {string} [showPasswordAriaLabel] - aria-label of the button when
1106
+ * the password is currently hidden. Plain text only.
1107
+ * @property {string} [hidePasswordAriaLabel] - aria-label of the button when
1108
+ * the password is currently visible. Plain text only.
1109
+ * @property {string} [passwordShownAnnouncement] - Screen reader
1110
+ * announcement to make when the password has just become visible.
1111
+ * Plain text only.
1112
+ * @property {string} [passwordHiddenAnnouncement] - Screen reader
1113
+ * announcement to make when the password has just been hidden.
1114
+ * Plain text only.
1115
+ */
1116
+
1117
+ /**
1118
+ * @typedef {import('../../common/index.mjs').Schema} Schema
1119
+ * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1120
+ */
1121
+ PasswordInput.moduleName = 'govuk-password-input';
1122
+ PasswordInput.defaults = Object.freeze({
1123
+ i18n: {
1124
+ showPassword: 'Show',
1125
+ hidePassword: 'Hide',
1126
+ showPasswordAriaLabel: 'Show password',
1127
+ hidePasswordAriaLabel: 'Hide password',
1128
+ passwordShownAnnouncement: 'Your password is visible',
1129
+ passwordHiddenAnnouncement: 'Your password is hidden'
1130
+ }
1131
+ });
1132
+ PasswordInput.schema = Object.freeze({
1133
+ properties: {
1134
+ i18n: {
1135
+ type: 'object'
1136
+ }
1137
+ }
1138
+ });
1139
+
1140
+ /**
1141
+ * Radios component
1142
+ *
1143
+ * @preserve
1144
+ */
1145
+ class Radios extends GOVUKFrontendComponent {
1146
+ /**
1147
+ * Radios can be associated with a 'conditionally revealed' content block –
1148
+ * for example, a radio for 'Phone' could reveal an additional form field for
1149
+ * the user to enter their phone number.
1150
+ *
1151
+ * These associations are made using a `data-aria-controls` attribute, which
1152
+ * is promoted to an aria-controls attribute during initialisation.
1153
+ *
1154
+ * We also need to restore the state of any conditional reveals on the page
1155
+ * (for example if the user has navigated back), and set up event handlers to
1156
+ * keep the reveal in sync with the radio state.
1157
+ *
1158
+ * @param {Element | null} $module - HTML element to use for radios
1159
+ */
1160
+ constructor($module) {
1161
+ super();
1162
+ this.$module = void 0;
1163
+ this.$inputs = void 0;
1164
+ if (!($module instanceof HTMLElement)) {
1165
+ throw new ElementError({
1166
+ componentName: 'Radios',
1167
+ element: $module,
1168
+ identifier: 'Root element (`$module`)'
1169
+ });
1170
+ }
1171
+ const $inputs = $module.querySelectorAll('input[type="radio"]');
1172
+ if (!$inputs.length) {
1173
+ throw new ElementError({
1174
+ componentName: 'Radios',
1175
+ identifier: 'Form inputs (`<input type="radio">`)'
1176
+ });
1177
+ }
1178
+ this.$module = $module;
1179
+ this.$inputs = $inputs;
1180
+ this.$inputs.forEach($input => {
1181
+ const targetId = $input.getAttribute('data-aria-controls');
1182
+ if (!targetId) {
1183
+ return;
1184
+ }
1185
+ if (!document.getElementById(targetId)) {
1186
+ throw new ElementError({
1187
+ componentName: 'Radios',
1188
+ identifier: `Conditional reveal (\`id="${targetId}"\`)`
1189
+ });
1190
+ }
1191
+ $input.setAttribute('aria-controls', targetId);
1192
+ $input.removeAttribute('data-aria-controls');
1193
+ });
1194
+ window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1195
+ this.syncAllConditionalReveals();
1196
+ this.$module.addEventListener('click', event => this.handleClick(event));
1197
+ }
1198
+ syncAllConditionalReveals() {
1199
+ this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1200
+ }
1201
+ syncConditionalRevealWithInputState($input) {
1202
+ const targetId = $input.getAttribute('aria-controls');
1203
+ if (!targetId) {
1204
+ return;
1205
+ }
1206
+ const $target = document.getElementById(targetId);
1207
+ if ($target != null && $target.classList.contains('govuk-radios__conditional')) {
1208
+ const inputIsChecked = $input.checked;
1209
+ $input.setAttribute('aria-expanded', inputIsChecked.toString());
1210
+ $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1211
+ }
1212
+ }
1213
+ handleClick(event) {
1214
+ const $clickedInput = event.target;
1215
+ if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'radio') {
1216
+ return;
1217
+ }
1218
+ const $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1219
+ const $clickedInputForm = $clickedInput.form;
1220
+ const $clickedInputName = $clickedInput.name;
1221
+ $allInputs.forEach($input => {
1222
+ const hasSameFormOwner = $input.form === $clickedInputForm;
1223
+ const hasSameName = $input.name === $clickedInputName;
1224
+ if (hasSameName && hasSameFormOwner) {
1225
+ this.syncConditionalRevealWithInputState($input);
1226
+ }
1227
+ });
1228
+ }
1229
+ }
1230
+ Radios.moduleName = 'govuk-radios';
1231
+
1232
+ function initAll(config) {
1233
+ let _config$scope;
1234
+ config = typeof config !== 'undefined' ? config : {};
1235
+ if (!isSupported()) {
1236
+ console.log(new SupportError());
1237
+ return;
1238
+ }
1239
+ const components = [[Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [Radios], [PasswordInput, config.passwordInput]];
1240
+ const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
1241
+ components.forEach(([Component, config]) => {
1242
+ const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
1243
+ $elements.forEach($element => {
1244
+ try {
1245
+ 'defaults' in Component ? new Component($element, config) : new Component($element);
1246
+ } catch (error) {
1247
+ console.log(error);
1248
+ }
1249
+ });
1250
+ });
1251
+ }
1252
+
1253
+ export { Button, CharacterCount, Checkboxes, ErrorSummary, PasswordInput, Radios, initAll };