katalyst-govuk-formbuilder 1.9.1 → 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/katalyst/govuk/formbuilder.scss +4 -4
  3. data/lib/katalyst/govuk/formbuilder/engine.rb +1 -1
  4. data/node_modules/govuk-frontend/dist/govuk/_base.scss +5 -0
  5. data/node_modules/govuk-frontend/dist/govuk/all.scss +3 -0
  6. data/node_modules/govuk-frontend/dist/govuk/components/_all.scss +10 -0
  7. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/components/_all.scss → node_modules/govuk-frontend/dist/govuk/components/_index.scss} +1 -1
  8. data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/error-summary/_index.scss +10 -3
  9. data/node_modules/govuk-frontend/dist/govuk/core/_all.scss +10 -0
  10. data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_govuk-frontend-properties.scss +1 -1
  11. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/core/_all.scss → node_modules/govuk-frontend/dist/govuk/core/_index.scss} +1 -1
  12. data/node_modules/govuk-frontend/dist/govuk/helpers/_all.scss +10 -0
  13. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/helpers/_all.scss → node_modules/govuk-frontend/dist/govuk/helpers/_index.scss} +1 -1
  14. data/node_modules/govuk-frontend/dist/govuk/index.scss +11 -0
  15. data/node_modules/govuk-frontend/dist/govuk/objects/_all.scss +10 -0
  16. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/objects/_all.scss → node_modules/govuk-frontend/dist/govuk/objects/_index.scss} +1 -1
  17. data/node_modules/govuk-frontend/dist/govuk/overrides/_all.scss +9 -0
  18. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/overrides/_all.scss → node_modules/govuk-frontend/dist/govuk/overrides/_index.scss} +1 -1
  19. data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/overrides/_typography.scss +6 -0
  20. data/node_modules/govuk-frontend/dist/govuk/settings/_all.scss +10 -0
  21. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/settings/_all.scss → node_modules/govuk-frontend/dist/govuk/settings/_index.scss} +1 -1
  22. data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_warnings.scss +8 -3
  23. data/node_modules/govuk-frontend/dist/govuk/tools/_all.scss +10 -0
  24. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/tools/_all.scss → node_modules/govuk-frontend/dist/govuk/tools/_index.scss} +1 -1
  25. data/node_modules/govuk-frontend/dist/govuk/utilities/_all.scss +10 -0
  26. data/{vendor/assets/stylesheets/govuk-frontend/dist/govuk/utilities/_all.scss → node_modules/govuk-frontend/dist/govuk/utilities/_index.scss} +1 -1
  27. metadata +143 -137
  28. data/app/assets/builds/katalyst/govuk/formbuilder.css +0 -4237
  29. data/app/assets/builds/katalyst/govuk/formbuilder.js +0 -2306
  30. data/app/assets/builds/katalyst/govuk/formbuilder.min.js +0 -91
  31. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/_base.scss +0 -5
  32. data/vendor/assets/stylesheets/govuk-frontend/dist/govuk/all.scss +0 -11
  33. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/accordion/_accordion.scss +0 -0
  34. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/accordion/_index.scss +0 -0
  35. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/back-link/_back-link.scss +0 -0
  36. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/back-link/_index.scss +0 -0
  37. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/breadcrumbs/_breadcrumbs.scss +0 -0
  38. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/breadcrumbs/_index.scss +0 -0
  39. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/button/_button.scss +0 -0
  40. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/button/_index.scss +0 -0
  41. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/character-count/_character-count.scss +0 -0
  42. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/character-count/_index.scss +0 -0
  43. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/checkboxes/_checkboxes.scss +0 -0
  44. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/checkboxes/_index.scss +0 -0
  45. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/cookie-banner/_cookie-banner.scss +0 -0
  46. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/cookie-banner/_index.scss +0 -0
  47. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/date-input/_date-input.scss +0 -0
  48. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/date-input/_index.scss +0 -0
  49. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/details/_details.scss +0 -0
  50. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/details/_index.scss +0 -0
  51. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/error-message/_error-message.scss +0 -0
  52. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/error-message/_index.scss +0 -0
  53. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/error-summary/_error-summary.scss +0 -0
  54. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/exit-this-page/_exit-this-page.scss +0 -0
  55. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/exit-this-page/_index.scss +0 -0
  56. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/fieldset/_fieldset.scss +0 -0
  57. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/fieldset/_index.scss +0 -0
  58. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/file-upload/_file-upload.scss +0 -0
  59. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/file-upload/_index.scss +0 -0
  60. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/footer/_footer.scss +0 -0
  61. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/footer/_index.scss +0 -0
  62. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/header/_header.scss +0 -0
  63. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/header/_index.scss +0 -0
  64. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/hint/_hint.scss +0 -0
  65. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/hint/_index.scss +0 -0
  66. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/input/_index.scss +0 -0
  67. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/input/_input.scss +0 -0
  68. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/inset-text/_index.scss +0 -0
  69. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/inset-text/_inset-text.scss +0 -0
  70. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/label/_index.scss +0 -0
  71. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/label/_label.scss +0 -0
  72. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/notification-banner/_index.scss +0 -0
  73. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/notification-banner/_notification-banner.scss +0 -0
  74. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/pagination/_index.scss +0 -0
  75. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/pagination/_pagination.scss +0 -0
  76. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/panel/_index.scss +0 -0
  77. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/panel/_panel.scss +0 -0
  78. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/password-input/_index.scss +0 -0
  79. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/password-input/_password-input.scss +0 -0
  80. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/phase-banner/_index.scss +0 -0
  81. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/phase-banner/_phase-banner.scss +0 -0
  82. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/radios/_index.scss +0 -0
  83. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/radios/_radios.scss +0 -0
  84. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/select/_index.scss +0 -0
  85. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/select/_select.scss +0 -0
  86. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/skip-link/_index.scss +0 -0
  87. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/skip-link/_skip-link.scss +0 -0
  88. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/summary-list/_index.scss +0 -0
  89. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/summary-list/_summary-list.scss +0 -0
  90. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/table/_index.scss +0 -0
  91. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/table/_table.scss +0 -0
  92. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/tabs/_index.scss +0 -0
  93. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/tabs/_tabs.scss +0 -0
  94. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/tag/_index.scss +0 -0
  95. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/tag/_tag.scss +0 -0
  96. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/task-list/_index.scss +0 -0
  97. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/task-list/_task-list.scss +0 -0
  98. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/textarea/_index.scss +0 -0
  99. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/textarea/_textarea.scss +0 -0
  100. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/warning-text/_index.scss +0 -0
  101. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/components/warning-text/_warning-text.scss +0 -0
  102. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_global-styles.scss +0 -0
  103. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_links.scss +0 -0
  104. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_lists.scss +0 -0
  105. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_section-break.scss +0 -0
  106. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/core/_typography.scss +0 -0
  107. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_clearfix.scss +0 -0
  108. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_colour.scss +0 -0
  109. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_device-pixels.scss +0 -0
  110. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_focused.scss +0 -0
  111. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_font-faces.scss +0 -0
  112. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_grid.scss +0 -0
  113. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_links.scss +0 -0
  114. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_media-queries.scss +0 -0
  115. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_shape-arrow.scss +0 -0
  116. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_spacing.scss +0 -0
  117. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_typography.scss +0 -0
  118. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/helpers/_visually-hidden.scss +0 -0
  119. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_button-group.scss +0 -0
  120. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_form-group.scss +0 -0
  121. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_grid.scss +0 -0
  122. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_main-wrapper.scss +0 -0
  123. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_template.scss +0 -0
  124. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/objects/_width-container.scss +0 -0
  125. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/overrides/_display.scss +0 -0
  126. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/overrides/_spacing.scss +0 -0
  127. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/overrides/_text-align.scss +0 -0
  128. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/overrides/_width.scss +0 -0
  129. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_assets.scss +0 -0
  130. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_colours-applied.scss +0 -0
  131. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_colours-organisations.scss +0 -0
  132. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_colours-palette.scss +0 -0
  133. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_global-styles.scss +0 -0
  134. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_links.scss +0 -0
  135. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_measurements.scss +0 -0
  136. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_media-queries.scss +0 -0
  137. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_spacing.scss +0 -0
  138. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_typography-font.scss +0 -0
  139. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/settings/_typography-responsive.scss +0 -0
  140. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/tools/_exports.scss +0 -0
  141. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/tools/_font-url.scss +0 -0
  142. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/tools/_image-url.scss +0 -0
  143. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/tools/_px-to-em.scss +0 -0
  144. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/tools/_px-to-rem.scss +0 -0
  145. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/utilities/_clearfix.scss +0 -0
  146. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/utilities/_visually-hidden.scss +0 -0
  147. /data/{vendor/assets/stylesheets → node_modules}/govuk-frontend/dist/govuk/vendor/_sass-mq.scss +0 -0
@@ -1,2306 +0,0 @@
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 getBreakpoint(name) {
74
- const property = `--govuk-frontend-breakpoint-${name}`;
75
- const value = window.getComputedStyle(document.documentElement).getPropertyValue(property);
76
- return {
77
- property,
78
- value: value || undefined
79
- };
80
- }
81
- function setFocus($element, options = {}) {
82
- var _options$onBeforeFocu;
83
- const isFocusable = $element.getAttribute('tabindex');
84
- if (!isFocusable) {
85
- $element.setAttribute('tabindex', '-1');
86
- }
87
- function onFocus() {
88
- $element.addEventListener('blur', onBlur, {
89
- once: true
90
- });
91
- }
92
- function onBlur() {
93
- var _options$onBlur;
94
- (_options$onBlur = options.onBlur) == null || _options$onBlur.call($element);
95
- if (!isFocusable) {
96
- $element.removeAttribute('tabindex');
97
- }
98
- }
99
- $element.addEventListener('focus', onFocus, {
100
- once: true
101
- });
102
- (_options$onBeforeFocu = options.onBeforeFocus) == null || _options$onBeforeFocu.call($element);
103
- $element.focus();
104
- }
105
- function isSupported($scope = document.body) {
106
- if (!$scope) {
107
- return false;
108
- }
109
- return $scope.classList.contains('govuk-frontend-supported');
110
- }
111
- function validateConfig(schema, config) {
112
- const validationErrors = [];
113
- for (const [name, conditions] of Object.entries(schema)) {
114
- const errors = [];
115
- if (Array.isArray(conditions)) {
116
- for (const {
117
- required,
118
- errorMessage
119
- } of conditions) {
120
- if (!required.every(key => !!config[key])) {
121
- errors.push(errorMessage);
122
- }
123
- }
124
- if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) {
125
- validationErrors.push(...errors);
126
- }
127
- }
128
- }
129
- return validationErrors;
130
- }
131
- function isArray(option) {
132
- return Array.isArray(option);
133
- }
134
- function isObject(option) {
135
- return !!option && typeof option === 'object' && !isArray(option);
136
- }
137
-
138
- function normaliseDataset(Component, dataset) {
139
- const out = {};
140
- for (const [field, property] of Object.entries(Component.schema.properties)) {
141
- if (field in dataset) {
142
- out[field] = normaliseString(dataset[field], property);
143
- }
144
- if ((property == null ? void 0 : property.type) === 'object') {
145
- out[field] = extractConfigByNamespace(Component, dataset, field);
146
- }
147
- }
148
- return out;
149
- }
150
-
151
- class GOVUKFrontendError extends Error {
152
- constructor(...args) {
153
- super(...args);
154
- this.name = 'GOVUKFrontendError';
155
- }
156
- }
157
- class SupportError extends GOVUKFrontendError {
158
- /**
159
- * Checks if GOV.UK Frontend is supported on this page
160
- *
161
- * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
162
- */
163
- constructor($scope = document.body) {
164
- 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';
165
- super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
166
- this.name = 'SupportError';
167
- }
168
- }
169
- class ConfigError extends GOVUKFrontendError {
170
- constructor(...args) {
171
- super(...args);
172
- this.name = 'ConfigError';
173
- }
174
- }
175
- class ElementError extends GOVUKFrontendError {
176
- constructor(messageOrOptions) {
177
- let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
178
- if (typeof messageOrOptions === 'object') {
179
- const {
180
- componentName,
181
- identifier,
182
- element,
183
- expectedType
184
- } = messageOrOptions;
185
- message = `${componentName}: ${identifier}`;
186
- message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
187
- }
188
- super(message);
189
- this.name = 'ElementError';
190
- }
191
- }
192
-
193
- class GOVUKFrontendComponent {
194
- constructor() {
195
- this.checkSupport();
196
- }
197
- checkSupport() {
198
- if (!isSupported()) {
199
- throw new SupportError();
200
- }
201
- }
202
- }
203
-
204
- class I18n {
205
- constructor(translations = {}, config = {}) {
206
- var _config$locale;
207
- this.translations = void 0;
208
- this.locale = void 0;
209
- this.translations = translations;
210
- this.locale = (_config$locale = config.locale) != null ? _config$locale : document.documentElement.lang || 'en';
211
- }
212
- t(lookupKey, options) {
213
- if (!lookupKey) {
214
- throw new Error('i18n: lookup key missing');
215
- }
216
- let translation = this.translations[lookupKey];
217
- if (typeof (options == null ? void 0 : options.count) === 'number' && typeof translation === 'object') {
218
- const translationPluralForm = translation[this.getPluralSuffix(lookupKey, options.count)];
219
- if (translationPluralForm) {
220
- translation = translationPluralForm;
221
- }
222
- }
223
- if (typeof translation === 'string') {
224
- if (translation.match(/%{(.\S+)}/)) {
225
- if (!options) {
226
- throw new Error('i18n: cannot replace placeholders in string if no option data provided');
227
- }
228
- return this.replacePlaceholders(translation, options);
229
- }
230
- return translation;
231
- }
232
- return lookupKey;
233
- }
234
- replacePlaceholders(translationString, options) {
235
- const formatter = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : undefined;
236
- return translationString.replace(/%{(.\S+)}/g, function (placeholderWithBraces, placeholderKey) {
237
- if (Object.prototype.hasOwnProperty.call(options, placeholderKey)) {
238
- const placeholderValue = options[placeholderKey];
239
- if (placeholderValue === false || typeof placeholderValue !== 'number' && typeof placeholderValue !== 'string') {
240
- return '';
241
- }
242
- if (typeof placeholderValue === 'number') {
243
- return formatter ? formatter.format(placeholderValue) : `${placeholderValue}`;
244
- }
245
- return placeholderValue;
246
- }
247
- throw new Error(`i18n: no data found to replace ${placeholderWithBraces} placeholder in string`);
248
- });
249
- }
250
- hasIntlPluralRulesSupport() {
251
- return Boolean('PluralRules' in window.Intl && Intl.PluralRules.supportedLocalesOf(this.locale).length);
252
- }
253
- getPluralSuffix(lookupKey, count) {
254
- count = Number(count);
255
- if (!isFinite(count)) {
256
- return 'other';
257
- }
258
- const translation = this.translations[lookupKey];
259
- const preferredForm = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(count) : this.selectPluralFormUsingFallbackRules(count);
260
- if (typeof translation === 'object') {
261
- if (preferredForm in translation) {
262
- return preferredForm;
263
- } else if ('other' in translation) {
264
- console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`);
265
- return 'other';
266
- }
267
- }
268
- throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`);
269
- }
270
- selectPluralFormUsingFallbackRules(count) {
271
- count = Math.abs(Math.floor(count));
272
- const ruleset = this.getPluralRulesForLocale();
273
- if (ruleset) {
274
- return I18n.pluralRules[ruleset](count);
275
- }
276
- return 'other';
277
- }
278
- getPluralRulesForLocale() {
279
- const localeShort = this.locale.split('-')[0];
280
- for (const pluralRule in I18n.pluralRulesMap) {
281
- const languages = I18n.pluralRulesMap[pluralRule];
282
- if (languages.includes(this.locale) || languages.includes(localeShort)) {
283
- return pluralRule;
284
- }
285
- }
286
- }
287
- }
288
- I18n.pluralRulesMap = {
289
- arabic: ['ar'],
290
- chinese: ['my', 'zh', 'id', 'ja', 'jv', 'ko', 'ms', 'th', 'vi'],
291
- french: ['hy', 'bn', 'fr', 'gu', 'hi', 'fa', 'pa', 'zu'],
292
- 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'],
293
- irish: ['ga'],
294
- russian: ['ru', 'uk'],
295
- scottish: ['gd'],
296
- spanish: ['pt-PT', 'it', 'es'],
297
- welsh: ['cy']
298
- };
299
- I18n.pluralRules = {
300
- arabic(n) {
301
- if (n === 0) {
302
- return 'zero';
303
- }
304
- if (n === 1) {
305
- return 'one';
306
- }
307
- if (n === 2) {
308
- return 'two';
309
- }
310
- if (n % 100 >= 3 && n % 100 <= 10) {
311
- return 'few';
312
- }
313
- if (n % 100 >= 11 && n % 100 <= 99) {
314
- return 'many';
315
- }
316
- return 'other';
317
- },
318
- chinese() {
319
- return 'other';
320
- },
321
- french(n) {
322
- return n === 0 || n === 1 ? 'one' : 'other';
323
- },
324
- german(n) {
325
- return n === 1 ? 'one' : 'other';
326
- },
327
- irish(n) {
328
- if (n === 1) {
329
- return 'one';
330
- }
331
- if (n === 2) {
332
- return 'two';
333
- }
334
- if (n >= 3 && n <= 6) {
335
- return 'few';
336
- }
337
- if (n >= 7 && n <= 10) {
338
- return 'many';
339
- }
340
- return 'other';
341
- },
342
- russian(n) {
343
- const lastTwo = n % 100;
344
- const last = lastTwo % 10;
345
- if (last === 1 && lastTwo !== 11) {
346
- return 'one';
347
- }
348
- if (last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14)) {
349
- return 'few';
350
- }
351
- if (last === 0 || last >= 5 && last <= 9 || lastTwo >= 11 && lastTwo <= 14) {
352
- return 'many';
353
- }
354
- return 'other';
355
- },
356
- scottish(n) {
357
- if (n === 1 || n === 11) {
358
- return 'one';
359
- }
360
- if (n === 2 || n === 12) {
361
- return 'two';
362
- }
363
- if (n >= 3 && n <= 10 || n >= 13 && n <= 19) {
364
- return 'few';
365
- }
366
- return 'other';
367
- },
368
- spanish(n) {
369
- if (n === 1) {
370
- return 'one';
371
- }
372
- if (n % 1000000 === 0 && n !== 0) {
373
- return 'many';
374
- }
375
- return 'other';
376
- },
377
- welsh(n) {
378
- if (n === 0) {
379
- return 'zero';
380
- }
381
- if (n === 1) {
382
- return 'one';
383
- }
384
- if (n === 2) {
385
- return 'two';
386
- }
387
- if (n === 3) {
388
- return 'few';
389
- }
390
- if (n === 6) {
391
- return 'many';
392
- }
393
- return 'other';
394
- }
395
- };
396
-
397
- /**
398
- * Accordion component
399
- *
400
- * This allows a collection of sections to be collapsed by default, showing only
401
- * their headers. Sections can be expanded or collapsed individually by clicking
402
- * their headers. A "Show all sections" button is also added to the top of the
403
- * accordion, which switches to "Hide all sections" when all the sections are
404
- * expanded.
405
- *
406
- * The state of each section is saved to the DOM via the `aria-expanded`
407
- * attribute, which also provides accessibility.
408
- *
409
- * @preserve
410
- */
411
- class Accordion extends GOVUKFrontendComponent {
412
- /**
413
- * @param {Element | null} $module - HTML element to use for accordion
414
- * @param {AccordionConfig} [config] - Accordion config
415
- */
416
- constructor($module, config = {}) {
417
- super();
418
- this.$module = void 0;
419
- this.config = void 0;
420
- this.i18n = void 0;
421
- this.controlsClass = 'govuk-accordion__controls';
422
- this.showAllClass = 'govuk-accordion__show-all';
423
- this.showAllTextClass = 'govuk-accordion__show-all-text';
424
- this.sectionClass = 'govuk-accordion__section';
425
- this.sectionExpandedClass = 'govuk-accordion__section--expanded';
426
- this.sectionButtonClass = 'govuk-accordion__section-button';
427
- this.sectionHeaderClass = 'govuk-accordion__section-header';
428
- this.sectionHeadingClass = 'govuk-accordion__section-heading';
429
- this.sectionHeadingDividerClass = 'govuk-accordion__section-heading-divider';
430
- this.sectionHeadingTextClass = 'govuk-accordion__section-heading-text';
431
- this.sectionHeadingTextFocusClass = 'govuk-accordion__section-heading-text-focus';
432
- this.sectionShowHideToggleClass = 'govuk-accordion__section-toggle';
433
- this.sectionShowHideToggleFocusClass = 'govuk-accordion__section-toggle-focus';
434
- this.sectionShowHideTextClass = 'govuk-accordion__section-toggle-text';
435
- this.upChevronIconClass = 'govuk-accordion-nav__chevron';
436
- this.downChevronIconClass = 'govuk-accordion-nav__chevron--down';
437
- this.sectionSummaryClass = 'govuk-accordion__section-summary';
438
- this.sectionSummaryFocusClass = 'govuk-accordion__section-summary-focus';
439
- this.sectionContentClass = 'govuk-accordion__section-content';
440
- this.$sections = void 0;
441
- this.browserSupportsSessionStorage = false;
442
- this.$showAllButton = null;
443
- this.$showAllIcon = null;
444
- this.$showAllText = null;
445
- if (!($module instanceof HTMLElement)) {
446
- throw new ElementError({
447
- componentName: 'Accordion',
448
- element: $module,
449
- identifier: 'Root element (`$module`)'
450
- });
451
- }
452
- this.$module = $module;
453
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, $module.dataset));
454
- this.i18n = new I18n(this.config.i18n);
455
- const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
456
- if (!$sections.length) {
457
- throw new ElementError({
458
- componentName: 'Accordion',
459
- identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
460
- });
461
- }
462
- this.$sections = $sections;
463
- this.browserSupportsSessionStorage = helper.checkForSessionStorage();
464
- this.initControls();
465
- this.initSectionHeaders();
466
- const areAllSectionsOpen = this.checkIfAllSectionsOpen();
467
- this.updateShowAllButton(areAllSectionsOpen);
468
- }
469
- initControls() {
470
- this.$showAllButton = document.createElement('button');
471
- this.$showAllButton.setAttribute('type', 'button');
472
- this.$showAllButton.setAttribute('class', this.showAllClass);
473
- this.$showAllButton.setAttribute('aria-expanded', 'false');
474
- this.$showAllIcon = document.createElement('span');
475
- this.$showAllIcon.classList.add(this.upChevronIconClass);
476
- this.$showAllButton.appendChild(this.$showAllIcon);
477
- const $accordionControls = document.createElement('div');
478
- $accordionControls.setAttribute('class', this.controlsClass);
479
- $accordionControls.appendChild(this.$showAllButton);
480
- this.$module.insertBefore($accordionControls, this.$module.firstChild);
481
- this.$showAllText = document.createElement('span');
482
- this.$showAllText.classList.add(this.showAllTextClass);
483
- this.$showAllButton.appendChild(this.$showAllText);
484
- this.$showAllButton.addEventListener('click', () => this.onShowOrHideAllToggle());
485
- if ('onbeforematch' in document) {
486
- document.addEventListener('beforematch', event => this.onBeforeMatch(event));
487
- }
488
- }
489
- initSectionHeaders() {
490
- this.$sections.forEach(($section, i) => {
491
- const $header = $section.querySelector(`.${this.sectionHeaderClass}`);
492
- if (!$header) {
493
- throw new ElementError({
494
- componentName: 'Accordion',
495
- identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
496
- });
497
- }
498
- this.constructHeaderMarkup($header, i);
499
- this.setExpanded(this.isExpanded($section), $section);
500
- $header.addEventListener('click', () => this.onSectionToggle($section));
501
- this.setInitialState($section);
502
- });
503
- }
504
- constructHeaderMarkup($header, index) {
505
- const $span = $header.querySelector(`.${this.sectionButtonClass}`);
506
- const $heading = $header.querySelector(`.${this.sectionHeadingClass}`);
507
- const $summary = $header.querySelector(`.${this.sectionSummaryClass}`);
508
- if (!$heading) {
509
- throw new ElementError({
510
- componentName: 'Accordion',
511
- identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
512
- });
513
- }
514
- if (!$span) {
515
- throw new ElementError({
516
- componentName: 'Accordion',
517
- identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
518
- });
519
- }
520
- const $button = document.createElement('button');
521
- $button.setAttribute('type', 'button');
522
- $button.setAttribute('aria-controls', `${this.$module.id}-content-${index + 1}`);
523
- for (const attr of Array.from($span.attributes)) {
524
- if (attr.nodeName !== 'id') {
525
- $button.setAttribute(attr.nodeName, `${attr.nodeValue}`);
526
- }
527
- }
528
- const $headingText = document.createElement('span');
529
- $headingText.classList.add(this.sectionHeadingTextClass);
530
- $headingText.id = $span.id;
531
- const $headingTextFocus = document.createElement('span');
532
- $headingTextFocus.classList.add(this.sectionHeadingTextFocusClass);
533
- $headingText.appendChild($headingTextFocus);
534
- $headingTextFocus.innerHTML = $span.innerHTML;
535
- const $showHideToggle = document.createElement('span');
536
- $showHideToggle.classList.add(this.sectionShowHideToggleClass);
537
- $showHideToggle.setAttribute('data-nosnippet', '');
538
- const $showHideToggleFocus = document.createElement('span');
539
- $showHideToggleFocus.classList.add(this.sectionShowHideToggleFocusClass);
540
- $showHideToggle.appendChild($showHideToggleFocus);
541
- const $showHideText = document.createElement('span');
542
- const $showHideIcon = document.createElement('span');
543
- $showHideIcon.classList.add(this.upChevronIconClass);
544
- $showHideToggleFocus.appendChild($showHideIcon);
545
- $showHideText.classList.add(this.sectionShowHideTextClass);
546
- $showHideToggleFocus.appendChild($showHideText);
547
- $button.appendChild($headingText);
548
- $button.appendChild(this.getButtonPunctuationEl());
549
- if ($summary != null && $summary.parentNode) {
550
- const $summarySpan = document.createElement('span');
551
- const $summarySpanFocus = document.createElement('span');
552
- $summarySpanFocus.classList.add(this.sectionSummaryFocusClass);
553
- $summarySpan.appendChild($summarySpanFocus);
554
- for (const attr of Array.from($summary.attributes)) {
555
- $summarySpan.setAttribute(attr.nodeName, `${attr.nodeValue}`);
556
- }
557
- $summarySpanFocus.innerHTML = $summary.innerHTML;
558
- $summary.parentNode.replaceChild($summarySpan, $summary);
559
- $button.appendChild($summarySpan);
560
- $button.appendChild(this.getButtonPunctuationEl());
561
- }
562
- $button.appendChild($showHideToggle);
563
- $heading.removeChild($span);
564
- $heading.appendChild($button);
565
- }
566
- onBeforeMatch(event) {
567
- const $fragment = event.target;
568
- if (!($fragment instanceof Element)) {
569
- return;
570
- }
571
- const $section = $fragment.closest(`.${this.sectionClass}`);
572
- if ($section) {
573
- this.setExpanded(true, $section);
574
- }
575
- }
576
- onSectionToggle($section) {
577
- const expanded = this.isExpanded($section);
578
- this.setExpanded(!expanded, $section);
579
- this.storeState($section);
580
- }
581
- onShowOrHideAllToggle() {
582
- const nowExpanded = !this.checkIfAllSectionsOpen();
583
- this.$sections.forEach($section => {
584
- this.setExpanded(nowExpanded, $section);
585
- this.storeState($section);
586
- });
587
- this.updateShowAllButton(nowExpanded);
588
- }
589
- setExpanded(expanded, $section) {
590
- const $showHideIcon = $section.querySelector(`.${this.upChevronIconClass}`);
591
- const $showHideText = $section.querySelector(`.${this.sectionShowHideTextClass}`);
592
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
593
- const $content = $section.querySelector(`.${this.sectionContentClass}`);
594
- if (!$content) {
595
- throw new ElementError({
596
- componentName: 'Accordion',
597
- identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
598
- });
599
- }
600
- if (!$showHideIcon || !$showHideText || !$button) {
601
- return;
602
- }
603
- const newButtonText = expanded ? this.i18n.t('hideSection') : this.i18n.t('showSection');
604
- $showHideText.textContent = newButtonText;
605
- $button.setAttribute('aria-expanded', `${expanded}`);
606
- const ariaLabelParts = [];
607
- const $headingText = $section.querySelector(`.${this.sectionHeadingTextClass}`);
608
- if ($headingText) {
609
- ariaLabelParts.push(`${$headingText.textContent}`.trim());
610
- }
611
- const $summary = $section.querySelector(`.${this.sectionSummaryClass}`);
612
- if ($summary) {
613
- ariaLabelParts.push(`${$summary.textContent}`.trim());
614
- }
615
- const ariaLabelMessage = expanded ? this.i18n.t('hideSectionAriaLabel') : this.i18n.t('showSectionAriaLabel');
616
- ariaLabelParts.push(ariaLabelMessage);
617
- $button.setAttribute('aria-label', ariaLabelParts.join(' , '));
618
- if (expanded) {
619
- $content.removeAttribute('hidden');
620
- $section.classList.add(this.sectionExpandedClass);
621
- $showHideIcon.classList.remove(this.downChevronIconClass);
622
- } else {
623
- $content.setAttribute('hidden', 'until-found');
624
- $section.classList.remove(this.sectionExpandedClass);
625
- $showHideIcon.classList.add(this.downChevronIconClass);
626
- }
627
- const areAllSectionsOpen = this.checkIfAllSectionsOpen();
628
- this.updateShowAllButton(areAllSectionsOpen);
629
- }
630
- isExpanded($section) {
631
- return $section.classList.contains(this.sectionExpandedClass);
632
- }
633
- checkIfAllSectionsOpen() {
634
- const sectionsCount = this.$sections.length;
635
- const expandedSectionCount = this.$module.querySelectorAll(`.${this.sectionExpandedClass}`).length;
636
- const areAllSectionsOpen = sectionsCount === expandedSectionCount;
637
- return areAllSectionsOpen;
638
- }
639
- updateShowAllButton(expanded) {
640
- if (!this.$showAllButton || !this.$showAllText || !this.$showAllIcon) {
641
- return;
642
- }
643
- this.$showAllButton.setAttribute('aria-expanded', expanded.toString());
644
- this.$showAllText.textContent = expanded ? this.i18n.t('hideAllSections') : this.i18n.t('showAllSections');
645
- this.$showAllIcon.classList.toggle(this.downChevronIconClass, !expanded);
646
- }
647
- storeState($section) {
648
- if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
649
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
650
- if ($button) {
651
- const contentId = $button.getAttribute('aria-controls');
652
- const contentState = $button.getAttribute('aria-expanded');
653
- if (contentId && contentState) {
654
- window.sessionStorage.setItem(contentId, contentState);
655
- }
656
- }
657
- }
658
- }
659
- setInitialState($section) {
660
- if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
661
- const $button = $section.querySelector(`.${this.sectionButtonClass}`);
662
- if ($button) {
663
- const contentId = $button.getAttribute('aria-controls');
664
- const contentState = contentId ? window.sessionStorage.getItem(contentId) : null;
665
- if (contentState !== null) {
666
- this.setExpanded(contentState === 'true', $section);
667
- }
668
- }
669
- }
670
- }
671
- getButtonPunctuationEl() {
672
- const $punctuationEl = document.createElement('span');
673
- $punctuationEl.classList.add('govuk-visually-hidden', this.sectionHeadingDividerClass);
674
- $punctuationEl.innerHTML = ', ';
675
- return $punctuationEl;
676
- }
677
- }
678
- Accordion.moduleName = 'govuk-accordion';
679
- Accordion.defaults = Object.freeze({
680
- i18n: {
681
- hideAllSections: 'Hide all sections',
682
- hideSection: 'Hide',
683
- hideSectionAriaLabel: 'Hide this section',
684
- showAllSections: 'Show all sections',
685
- showSection: 'Show',
686
- showSectionAriaLabel: 'Show this section'
687
- },
688
- rememberExpanded: true
689
- });
690
- Accordion.schema = Object.freeze({
691
- properties: {
692
- i18n: {
693
- type: 'object'
694
- },
695
- rememberExpanded: {
696
- type: 'boolean'
697
- }
698
- }
699
- });
700
- const helper = {
701
- /**
702
- * Check for `window.sessionStorage`, and that it actually works.
703
- *
704
- * @returns {boolean} True if session storage is available
705
- */
706
- checkForSessionStorage: function () {
707
- const testString = 'this is the test string';
708
- let result;
709
- try {
710
- window.sessionStorage.setItem(testString, testString);
711
- result = window.sessionStorage.getItem(testString) === testString.toString();
712
- window.sessionStorage.removeItem(testString);
713
- return result;
714
- } catch (exception) {
715
- return false;
716
- }
717
- }
718
- };
719
-
720
- const DEBOUNCE_TIMEOUT_IN_SECONDS = 1;
721
-
722
- /**
723
- * JavaScript enhancements for the Button component
724
- *
725
- * @preserve
726
- */
727
- class Button extends GOVUKFrontendComponent {
728
- /**
729
- * @param {Element | null} $module - HTML element to use for button
730
- * @param {ButtonConfig} [config] - Button config
731
- */
732
- constructor($module, config = {}) {
733
- super();
734
- this.$module = void 0;
735
- this.config = void 0;
736
- this.debounceFormSubmitTimer = null;
737
- if (!($module instanceof HTMLElement)) {
738
- throw new ElementError({
739
- componentName: 'Button',
740
- element: $module,
741
- identifier: 'Root element (`$module`)'
742
- });
743
- }
744
- this.$module = $module;
745
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset));
746
- this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
747
- this.$module.addEventListener('click', event => this.debounce(event));
748
- }
749
- handleKeyDown(event) {
750
- const $target = event.target;
751
- if (event.key !== ' ') {
752
- return;
753
- }
754
- if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') {
755
- event.preventDefault();
756
- $target.click();
757
- }
758
- }
759
- debounce(event) {
760
- if (!this.config.preventDoubleClick) {
761
- return;
762
- }
763
- if (this.debounceFormSubmitTimer) {
764
- event.preventDefault();
765
- return false;
766
- }
767
- this.debounceFormSubmitTimer = window.setTimeout(() => {
768
- this.debounceFormSubmitTimer = null;
769
- }, DEBOUNCE_TIMEOUT_IN_SECONDS * 1000);
770
- }
771
- }
772
-
773
- /**
774
- * Button config
775
- *
776
- * @typedef {object} ButtonConfig
777
- * @property {boolean} [preventDoubleClick=false] - Prevent accidental double
778
- * clicks on submit buttons from submitting forms multiple times.
779
- */
780
-
781
- /**
782
- * @typedef {import('../../common/index.mjs').Schema} Schema
783
- */
784
- Button.moduleName = 'govuk-button';
785
- Button.defaults = Object.freeze({
786
- preventDoubleClick: false
787
- });
788
- Button.schema = Object.freeze({
789
- properties: {
790
- preventDoubleClick: {
791
- type: 'boolean'
792
- }
793
- }
794
- });
795
-
796
- function closestAttributeValue($element, attributeName) {
797
- const $closestElementWithAttribute = $element.closest(`[${attributeName}]`);
798
- return $closestElementWithAttribute ? $closestElementWithAttribute.getAttribute(attributeName) : null;
799
- }
800
-
801
- /**
802
- * Character count component
803
- *
804
- * Tracks the number of characters or words in the `.govuk-js-character-count`
805
- * `<textarea>` inside the element. Displays a message with the remaining number
806
- * of characters/words available, or the number of characters/words in excess.
807
- *
808
- * You can configure the message to only appear after a certain percentage
809
- * of the available characters/words has been entered.
810
- *
811
- * @preserve
812
- */
813
- class CharacterCount extends GOVUKFrontendComponent {
814
- /**
815
- * @param {Element | null} $module - HTML element to use for character count
816
- * @param {CharacterCountConfig} [config] - Character count config
817
- */
818
- constructor($module, config = {}) {
819
- var _ref, _this$config$maxwords;
820
- super();
821
- this.$module = void 0;
822
- this.$textarea = void 0;
823
- this.$visibleCountMessage = void 0;
824
- this.$screenReaderCountMessage = void 0;
825
- this.lastInputTimestamp = null;
826
- this.lastInputValue = '';
827
- this.valueChecker = null;
828
- this.config = void 0;
829
- this.i18n = void 0;
830
- this.maxLength = void 0;
831
- if (!($module instanceof HTMLElement)) {
832
- throw new ElementError({
833
- componentName: 'Character count',
834
- element: $module,
835
- identifier: 'Root element (`$module`)'
836
- });
837
- }
838
- const $textarea = $module.querySelector('.govuk-js-character-count');
839
- if (!($textarea instanceof HTMLTextAreaElement || $textarea instanceof HTMLInputElement)) {
840
- throw new ElementError({
841
- componentName: 'Character count',
842
- element: $textarea,
843
- expectedType: 'HTMLTextareaElement or HTMLInputElement',
844
- identifier: 'Form field (`.govuk-js-character-count`)'
845
- });
846
- }
847
- const datasetConfig = normaliseDataset(CharacterCount, $module.dataset);
848
- let configOverrides = {};
849
- if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) {
850
- configOverrides = {
851
- maxlength: undefined,
852
- maxwords: undefined
853
- };
854
- }
855
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
856
- const errors = validateConfig(CharacterCount.schema, this.config);
857
- if (errors[0]) {
858
- throw new ConfigError(`Character count: ${errors[0]}`);
859
- }
860
- this.i18n = new I18n(this.config.i18n, {
861
- locale: closestAttributeValue($module, 'lang')
862
- });
863
- this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity;
864
- this.$module = $module;
865
- this.$textarea = $textarea;
866
- const textareaDescriptionId = `${this.$textarea.id}-info`;
867
- const $textareaDescription = document.getElementById(textareaDescriptionId);
868
- if (!$textareaDescription) {
869
- throw new ElementError({
870
- componentName: 'Character count',
871
- element: $textareaDescription,
872
- identifier: `Count message (\`id="${textareaDescriptionId}"\`)`
873
- });
874
- }
875
- if (`${$textareaDescription.textContent}`.match(/^\s*$/)) {
876
- $textareaDescription.textContent = this.i18n.t('textareaDescription', {
877
- count: this.maxLength
878
- });
879
- }
880
- this.$textarea.insertAdjacentElement('afterend', $textareaDescription);
881
- const $screenReaderCountMessage = document.createElement('div');
882
- $screenReaderCountMessage.className = 'govuk-character-count__sr-status govuk-visually-hidden';
883
- $screenReaderCountMessage.setAttribute('aria-live', 'polite');
884
- this.$screenReaderCountMessage = $screenReaderCountMessage;
885
- $textareaDescription.insertAdjacentElement('afterend', $screenReaderCountMessage);
886
- const $visibleCountMessage = document.createElement('div');
887
- $visibleCountMessage.className = $textareaDescription.className;
888
- $visibleCountMessage.classList.add('govuk-character-count__status');
889
- $visibleCountMessage.setAttribute('aria-hidden', 'true');
890
- this.$visibleCountMessage = $visibleCountMessage;
891
- $textareaDescription.insertAdjacentElement('afterend', $visibleCountMessage);
892
- $textareaDescription.classList.add('govuk-visually-hidden');
893
- this.$textarea.removeAttribute('maxlength');
894
- this.bindChangeEvents();
895
- window.addEventListener('pageshow', () => this.updateCountMessage());
896
- this.updateCountMessage();
897
- }
898
- bindChangeEvents() {
899
- this.$textarea.addEventListener('keyup', () => this.handleKeyUp());
900
- this.$textarea.addEventListener('focus', () => this.handleFocus());
901
- this.$textarea.addEventListener('blur', () => this.handleBlur());
902
- }
903
- handleKeyUp() {
904
- this.updateVisibleCountMessage();
905
- this.lastInputTimestamp = Date.now();
906
- }
907
- handleFocus() {
908
- this.valueChecker = window.setInterval(() => {
909
- if (!this.lastInputTimestamp || Date.now() - 500 >= this.lastInputTimestamp) {
910
- this.updateIfValueChanged();
911
- }
912
- }, 1000);
913
- }
914
- handleBlur() {
915
- if (this.valueChecker) {
916
- window.clearInterval(this.valueChecker);
917
- }
918
- }
919
- updateIfValueChanged() {
920
- if (this.$textarea.value !== this.lastInputValue) {
921
- this.lastInputValue = this.$textarea.value;
922
- this.updateCountMessage();
923
- }
924
- }
925
- updateCountMessage() {
926
- this.updateVisibleCountMessage();
927
- this.updateScreenReaderCountMessage();
928
- }
929
- updateVisibleCountMessage() {
930
- const remainingNumber = this.maxLength - this.count(this.$textarea.value);
931
- const isError = remainingNumber < 0;
932
- this.$visibleCountMessage.classList.toggle('govuk-character-count__message--disabled', !this.isOverThreshold());
933
- this.$textarea.classList.toggle('govuk-textarea--error', isError);
934
- this.$visibleCountMessage.classList.toggle('govuk-error-message', isError);
935
- this.$visibleCountMessage.classList.toggle('govuk-hint', !isError);
936
- this.$visibleCountMessage.textContent = this.getCountMessage();
937
- }
938
- updateScreenReaderCountMessage() {
939
- if (this.isOverThreshold()) {
940
- this.$screenReaderCountMessage.removeAttribute('aria-hidden');
941
- } else {
942
- this.$screenReaderCountMessage.setAttribute('aria-hidden', 'true');
943
- }
944
- this.$screenReaderCountMessage.textContent = this.getCountMessage();
945
- }
946
- count(text) {
947
- if (this.config.maxwords) {
948
- var _text$match;
949
- const tokens = (_text$match = text.match(/\S+/g)) != null ? _text$match : [];
950
- return tokens.length;
951
- }
952
- return text.length;
953
- }
954
- getCountMessage() {
955
- const remainingNumber = this.maxLength - this.count(this.$textarea.value);
956
- const countType = this.config.maxwords ? 'words' : 'characters';
957
- return this.formatCountMessage(remainingNumber, countType);
958
- }
959
- formatCountMessage(remainingNumber, countType) {
960
- if (remainingNumber === 0) {
961
- return this.i18n.t(`${countType}AtLimit`);
962
- }
963
- const translationKeySuffix = remainingNumber < 0 ? 'OverLimit' : 'UnderLimit';
964
- return this.i18n.t(`${countType}${translationKeySuffix}`, {
965
- count: Math.abs(remainingNumber)
966
- });
967
- }
968
- isOverThreshold() {
969
- if (!this.config.threshold) {
970
- return true;
971
- }
972
- const currentLength = this.count(this.$textarea.value);
973
- const maxLength = this.maxLength;
974
- const thresholdValue = maxLength * this.config.threshold / 100;
975
- return thresholdValue <= currentLength;
976
- }
977
- }
978
-
979
- /**
980
- * Character count config
981
- *
982
- * @see {@link CharacterCount.defaults}
983
- * @typedef {object} CharacterCountConfig
984
- * @property {number} [maxlength] - The maximum number of characters.
985
- * If maxwords is provided, the maxlength option will be ignored.
986
- * @property {number} [maxwords] - The maximum number of words. If maxwords is
987
- * provided, the maxlength option will be ignored.
988
- * @property {number} [threshold=0] - The percentage value of the limit at
989
- * which point the count message is displayed. If this attribute is set, the
990
- * count message will be hidden by default.
991
- * @property {CharacterCountTranslations} [i18n=CharacterCount.defaults.i18n] - Character count translations
992
- */
993
-
994
- /**
995
- * Character count translations
996
- *
997
- * @see {@link CharacterCount.defaults.i18n}
998
- * @typedef {object} CharacterCountTranslations
999
- *
1000
- * Messages shown to users as they type. It provides feedback on how many words
1001
- * or characters they have remaining or if they are over the limit. This also
1002
- * includes a message used as an accessible description for the textarea.
1003
- * @property {TranslationPluralForms} [charactersUnderLimit] - Message displayed
1004
- * when the number of characters is under the configured maximum, `maxlength`.
1005
- * This message is displayed visually and through assistive technologies. The
1006
- * component will replace the `%{count}` placeholder with the number of
1007
- * remaining characters. This is a [pluralised list of
1008
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1009
- * @property {string} [charactersAtLimit] - Message displayed when the number of
1010
- * characters reaches the configured maximum, `maxlength`. This message is
1011
- * displayed visually and through assistive technologies.
1012
- * @property {TranslationPluralForms} [charactersOverLimit] - Message displayed
1013
- * when the number of characters is over the configured maximum, `maxlength`.
1014
- * This message is displayed visually and through assistive technologies. The
1015
- * component will replace the `%{count}` placeholder with the number of
1016
- * remaining characters. This is a [pluralised list of
1017
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1018
- * @property {TranslationPluralForms} [wordsUnderLimit] - Message displayed when
1019
- * the number of words is under the configured maximum, `maxlength`. This
1020
- * message is displayed visually and through assistive technologies. The
1021
- * component will replace the `%{count}` placeholder with the number of
1022
- * remaining words. This is a [pluralised list of
1023
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1024
- * @property {string} [wordsAtLimit] - Message displayed when the number of
1025
- * words reaches the configured maximum, `maxlength`. This message is
1026
- * displayed visually and through assistive technologies.
1027
- * @property {TranslationPluralForms} [wordsOverLimit] - Message displayed when
1028
- * the number of words is over the configured maximum, `maxlength`. This
1029
- * message is displayed visually and through assistive technologies. The
1030
- * component will replace the `%{count}` placeholder with the number of
1031
- * remaining words. This is a [pluralised list of
1032
- * messages](https://frontend.design-system.service.gov.uk/localise-govuk-frontend).
1033
- * @property {TranslationPluralForms} [textareaDescription] - Message made
1034
- * available to assistive technologies, if none is already present in the
1035
- * HTML, to describe that the component accepts only a limited amount of
1036
- * content. It is visible on the page when JavaScript is unavailable. The
1037
- * component will replace the `%{count}` placeholder with the value of the
1038
- * `maxlength` or `maxwords` parameter.
1039
- */
1040
-
1041
- /**
1042
- * @typedef {import('../../common/index.mjs').Schema} Schema
1043
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1044
- */
1045
- CharacterCount.moduleName = 'govuk-character-count';
1046
- CharacterCount.defaults = Object.freeze({
1047
- threshold: 0,
1048
- i18n: {
1049
- charactersUnderLimit: {
1050
- one: 'You have %{count} character remaining',
1051
- other: 'You have %{count} characters remaining'
1052
- },
1053
- charactersAtLimit: 'You have 0 characters remaining',
1054
- charactersOverLimit: {
1055
- one: 'You have %{count} character too many',
1056
- other: 'You have %{count} characters too many'
1057
- },
1058
- wordsUnderLimit: {
1059
- one: 'You have %{count} word remaining',
1060
- other: 'You have %{count} words remaining'
1061
- },
1062
- wordsAtLimit: 'You have 0 words remaining',
1063
- wordsOverLimit: {
1064
- one: 'You have %{count} word too many',
1065
- other: 'You have %{count} words too many'
1066
- },
1067
- textareaDescription: {
1068
- other: ''
1069
- }
1070
- }
1071
- });
1072
- CharacterCount.schema = Object.freeze({
1073
- properties: {
1074
- i18n: {
1075
- type: 'object'
1076
- },
1077
- maxwords: {
1078
- type: 'number'
1079
- },
1080
- maxlength: {
1081
- type: 'number'
1082
- },
1083
- threshold: {
1084
- type: 'number'
1085
- }
1086
- },
1087
- anyOf: [{
1088
- required: ['maxwords'],
1089
- errorMessage: 'Either "maxlength" or "maxwords" must be provided'
1090
- }, {
1091
- required: ['maxlength'],
1092
- errorMessage: 'Either "maxlength" or "maxwords" must be provided'
1093
- }]
1094
- });
1095
-
1096
- /**
1097
- * Checkboxes component
1098
- *
1099
- * @preserve
1100
- */
1101
- class Checkboxes extends GOVUKFrontendComponent {
1102
- /**
1103
- * Checkboxes can be associated with a 'conditionally revealed' content block
1104
- * – for example, a checkbox for 'Phone' could reveal an additional form field
1105
- * for the user to enter their phone number.
1106
- *
1107
- * These associations are made using a `data-aria-controls` attribute, which
1108
- * is promoted to an aria-controls attribute during initialisation.
1109
- *
1110
- * We also need to restore the state of any conditional reveals on the page
1111
- * (for example if the user has navigated back), and set up event handlers to
1112
- * keep the reveal in sync with the checkbox state.
1113
- *
1114
- * @param {Element | null} $module - HTML element to use for checkboxes
1115
- */
1116
- constructor($module) {
1117
- super();
1118
- this.$module = void 0;
1119
- this.$inputs = void 0;
1120
- if (!($module instanceof HTMLElement)) {
1121
- throw new ElementError({
1122
- componentName: 'Checkboxes',
1123
- element: $module,
1124
- identifier: 'Root element (`$module`)'
1125
- });
1126
- }
1127
- const $inputs = $module.querySelectorAll('input[type="checkbox"]');
1128
- if (!$inputs.length) {
1129
- throw new ElementError({
1130
- componentName: 'Checkboxes',
1131
- identifier: 'Form inputs (`<input type="checkbox">`)'
1132
- });
1133
- }
1134
- this.$module = $module;
1135
- this.$inputs = $inputs;
1136
- this.$inputs.forEach($input => {
1137
- const targetId = $input.getAttribute('data-aria-controls');
1138
- if (!targetId) {
1139
- return;
1140
- }
1141
- if (!document.getElementById(targetId)) {
1142
- throw new ElementError({
1143
- componentName: 'Checkboxes',
1144
- identifier: `Conditional reveal (\`id="${targetId}"\`)`
1145
- });
1146
- }
1147
- $input.setAttribute('aria-controls', targetId);
1148
- $input.removeAttribute('data-aria-controls');
1149
- });
1150
- window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1151
- this.syncAllConditionalReveals();
1152
- this.$module.addEventListener('click', event => this.handleClick(event));
1153
- }
1154
- syncAllConditionalReveals() {
1155
- this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1156
- }
1157
- syncConditionalRevealWithInputState($input) {
1158
- const targetId = $input.getAttribute('aria-controls');
1159
- if (!targetId) {
1160
- return;
1161
- }
1162
- const $target = document.getElementById(targetId);
1163
- if ($target != null && $target.classList.contains('govuk-checkboxes__conditional')) {
1164
- const inputIsChecked = $input.checked;
1165
- $input.setAttribute('aria-expanded', inputIsChecked.toString());
1166
- $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked);
1167
- }
1168
- }
1169
- unCheckAllInputsExcept($input) {
1170
- const allInputsWithSameName = document.querySelectorAll(`input[type="checkbox"][name="${$input.name}"]`);
1171
- allInputsWithSameName.forEach($inputWithSameName => {
1172
- const hasSameFormOwner = $input.form === $inputWithSameName.form;
1173
- if (hasSameFormOwner && $inputWithSameName !== $input) {
1174
- $inputWithSameName.checked = false;
1175
- this.syncConditionalRevealWithInputState($inputWithSameName);
1176
- }
1177
- });
1178
- }
1179
- unCheckExclusiveInputs($input) {
1180
- const allInputsWithSameNameAndExclusiveBehaviour = document.querySelectorAll(`input[data-behaviour="exclusive"][type="checkbox"][name="${$input.name}"]`);
1181
- allInputsWithSameNameAndExclusiveBehaviour.forEach($exclusiveInput => {
1182
- const hasSameFormOwner = $input.form === $exclusiveInput.form;
1183
- if (hasSameFormOwner) {
1184
- $exclusiveInput.checked = false;
1185
- this.syncConditionalRevealWithInputState($exclusiveInput);
1186
- }
1187
- });
1188
- }
1189
- handleClick(event) {
1190
- const $clickedInput = event.target;
1191
- if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'checkbox') {
1192
- return;
1193
- }
1194
- const hasAriaControls = $clickedInput.getAttribute('aria-controls');
1195
- if (hasAriaControls) {
1196
- this.syncConditionalRevealWithInputState($clickedInput);
1197
- }
1198
- if (!$clickedInput.checked) {
1199
- return;
1200
- }
1201
- const hasBehaviourExclusive = $clickedInput.getAttribute('data-behaviour') === 'exclusive';
1202
- if (hasBehaviourExclusive) {
1203
- this.unCheckAllInputsExcept($clickedInput);
1204
- } else {
1205
- this.unCheckExclusiveInputs($clickedInput);
1206
- }
1207
- }
1208
- }
1209
- Checkboxes.moduleName = 'govuk-checkboxes';
1210
-
1211
- /**
1212
- * Error summary component
1213
- *
1214
- * Takes focus on initialisation for accessible announcement, unless disabled in
1215
- * configuration.
1216
- *
1217
- * @preserve
1218
- */
1219
- class ErrorSummary extends GOVUKFrontendComponent {
1220
- /**
1221
- * @param {Element | null} $module - HTML element to use for error summary
1222
- * @param {ErrorSummaryConfig} [config] - Error summary config
1223
- */
1224
- constructor($module, config = {}) {
1225
- super();
1226
- this.$module = void 0;
1227
- this.config = void 0;
1228
- if (!($module instanceof HTMLElement)) {
1229
- throw new ElementError({
1230
- componentName: 'Error summary',
1231
- element: $module,
1232
- identifier: 'Root element (`$module`)'
1233
- });
1234
- }
1235
- this.$module = $module;
1236
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset));
1237
- if (!this.config.disableAutoFocus) {
1238
- setFocus(this.$module);
1239
- }
1240
- this.$module.addEventListener('click', event => this.handleClick(event));
1241
- }
1242
- handleClick(event) {
1243
- const $target = event.target;
1244
- if ($target && this.focusTarget($target)) {
1245
- event.preventDefault();
1246
- }
1247
- }
1248
- focusTarget($target) {
1249
- if (!($target instanceof HTMLAnchorElement)) {
1250
- return false;
1251
- }
1252
- const inputId = getFragmentFromUrl($target.href);
1253
- if (!inputId) {
1254
- return false;
1255
- }
1256
- const $input = document.getElementById(inputId);
1257
- if (!$input) {
1258
- return false;
1259
- }
1260
- const $legendOrLabel = this.getAssociatedLegendOrLabel($input);
1261
- if (!$legendOrLabel) {
1262
- return false;
1263
- }
1264
- $legendOrLabel.scrollIntoView();
1265
- $input.focus({
1266
- preventScroll: true
1267
- });
1268
- return true;
1269
- }
1270
- getAssociatedLegendOrLabel($input) {
1271
- var _document$querySelect;
1272
- const $fieldset = $input.closest('fieldset');
1273
- if ($fieldset) {
1274
- const $legends = $fieldset.getElementsByTagName('legend');
1275
- if ($legends.length) {
1276
- const $candidateLegend = $legends[0];
1277
- if ($input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio')) {
1278
- return $candidateLegend;
1279
- }
1280
- const legendTop = $candidateLegend.getBoundingClientRect().top;
1281
- const inputRect = $input.getBoundingClientRect();
1282
- if (inputRect.height && window.innerHeight) {
1283
- const inputBottom = inputRect.top + inputRect.height;
1284
- if (inputBottom - legendTop < window.innerHeight / 2) {
1285
- return $candidateLegend;
1286
- }
1287
- }
1288
- }
1289
- }
1290
- return (_document$querySelect = document.querySelector(`label[for='${$input.getAttribute('id')}']`)) != null ? _document$querySelect : $input.closest('label');
1291
- }
1292
- }
1293
-
1294
- /**
1295
- * Error summary config
1296
- *
1297
- * @typedef {object} ErrorSummaryConfig
1298
- * @property {boolean} [disableAutoFocus=false] - If set to `true` the error
1299
- * summary will not be focussed when the page loads.
1300
- */
1301
-
1302
- /**
1303
- * @typedef {import('../../common/index.mjs').Schema} Schema
1304
- */
1305
- ErrorSummary.moduleName = 'govuk-error-summary';
1306
- ErrorSummary.defaults = Object.freeze({
1307
- disableAutoFocus: false
1308
- });
1309
- ErrorSummary.schema = Object.freeze({
1310
- properties: {
1311
- disableAutoFocus: {
1312
- type: 'boolean'
1313
- }
1314
- }
1315
- });
1316
-
1317
- /**
1318
- * Exit this page component
1319
- *
1320
- * @preserve
1321
- */
1322
- class ExitThisPage extends GOVUKFrontendComponent {
1323
- /**
1324
- * @param {Element | null} $module - HTML element that wraps the Exit This Page button
1325
- * @param {ExitThisPageConfig} [config] - Exit This Page config
1326
- */
1327
- constructor($module, config = {}) {
1328
- super();
1329
- this.$module = void 0;
1330
- this.config = void 0;
1331
- this.i18n = void 0;
1332
- this.$button = void 0;
1333
- this.$skiplinkButton = null;
1334
- this.$updateSpan = null;
1335
- this.$indicatorContainer = null;
1336
- this.$overlay = null;
1337
- this.keypressCounter = 0;
1338
- this.lastKeyWasModified = false;
1339
- this.timeoutTime = 5000;
1340
- this.keypressTimeoutId = null;
1341
- this.timeoutMessageId = null;
1342
- if (!($module instanceof HTMLElement)) {
1343
- throw new ElementError({
1344
- componentName: 'Exit this page',
1345
- element: $module,
1346
- identifier: 'Root element (`$module`)'
1347
- });
1348
- }
1349
- const $button = $module.querySelector('.govuk-exit-this-page__button');
1350
- if (!($button instanceof HTMLAnchorElement)) {
1351
- throw new ElementError({
1352
- componentName: 'Exit this page',
1353
- element: $button,
1354
- expectedType: 'HTMLAnchorElement',
1355
- identifier: 'Button (`.govuk-exit-this-page__button`)'
1356
- });
1357
- }
1358
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, $module.dataset));
1359
- this.i18n = new I18n(this.config.i18n);
1360
- this.$module = $module;
1361
- this.$button = $button;
1362
- const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink');
1363
- if ($skiplinkButton instanceof HTMLAnchorElement) {
1364
- this.$skiplinkButton = $skiplinkButton;
1365
- }
1366
- this.buildIndicator();
1367
- this.initUpdateSpan();
1368
- this.initButtonClickHandler();
1369
- if (!('govukFrontendExitThisPageKeypress' in document.body.dataset)) {
1370
- document.addEventListener('keyup', this.handleKeypress.bind(this), true);
1371
- document.body.dataset.govukFrontendExitThisPageKeypress = 'true';
1372
- }
1373
- window.addEventListener('pageshow', this.resetPage.bind(this));
1374
- }
1375
- initUpdateSpan() {
1376
- this.$updateSpan = document.createElement('span');
1377
- this.$updateSpan.setAttribute('role', 'status');
1378
- this.$updateSpan.className = 'govuk-visually-hidden';
1379
- this.$module.appendChild(this.$updateSpan);
1380
- }
1381
- initButtonClickHandler() {
1382
- this.$button.addEventListener('click', this.handleClick.bind(this));
1383
- if (this.$skiplinkButton) {
1384
- this.$skiplinkButton.addEventListener('click', this.handleClick.bind(this));
1385
- }
1386
- }
1387
- buildIndicator() {
1388
- this.$indicatorContainer = document.createElement('div');
1389
- this.$indicatorContainer.className = 'govuk-exit-this-page__indicator';
1390
- this.$indicatorContainer.setAttribute('aria-hidden', 'true');
1391
- for (let i = 0; i < 3; i++) {
1392
- const $indicator = document.createElement('div');
1393
- $indicator.className = 'govuk-exit-this-page__indicator-light';
1394
- this.$indicatorContainer.appendChild($indicator);
1395
- }
1396
- this.$button.appendChild(this.$indicatorContainer);
1397
- }
1398
- updateIndicator() {
1399
- if (!this.$indicatorContainer) {
1400
- return;
1401
- }
1402
- this.$indicatorContainer.classList.toggle('govuk-exit-this-page__indicator--visible', this.keypressCounter > 0);
1403
- const $indicators = this.$indicatorContainer.querySelectorAll('.govuk-exit-this-page__indicator-light');
1404
- $indicators.forEach(($indicator, index) => {
1405
- $indicator.classList.toggle('govuk-exit-this-page__indicator-light--on', index < this.keypressCounter);
1406
- });
1407
- }
1408
- exitPage() {
1409
- if (!this.$updateSpan) {
1410
- return;
1411
- }
1412
- this.$updateSpan.textContent = '';
1413
- document.body.classList.add('govuk-exit-this-page-hide-content');
1414
- this.$overlay = document.createElement('div');
1415
- this.$overlay.className = 'govuk-exit-this-page-overlay';
1416
- this.$overlay.setAttribute('role', 'alert');
1417
- document.body.appendChild(this.$overlay);
1418
- this.$overlay.textContent = this.i18n.t('activated');
1419
- window.location.href = this.$button.href;
1420
- }
1421
- handleClick(event) {
1422
- event.preventDefault();
1423
- this.exitPage();
1424
- }
1425
- handleKeypress(event) {
1426
- if (!this.$updateSpan) {
1427
- return;
1428
- }
1429
- if (event.key === 'Shift' && !this.lastKeyWasModified) {
1430
- this.keypressCounter += 1;
1431
- this.updateIndicator();
1432
- if (this.timeoutMessageId) {
1433
- window.clearTimeout(this.timeoutMessageId);
1434
- this.timeoutMessageId = null;
1435
- }
1436
- if (this.keypressCounter >= 3) {
1437
- this.keypressCounter = 0;
1438
- if (this.keypressTimeoutId) {
1439
- window.clearTimeout(this.keypressTimeoutId);
1440
- this.keypressTimeoutId = null;
1441
- }
1442
- this.exitPage();
1443
- } else {
1444
- if (this.keypressCounter === 1) {
1445
- this.$updateSpan.textContent = this.i18n.t('pressTwoMoreTimes');
1446
- } else {
1447
- this.$updateSpan.textContent = this.i18n.t('pressOneMoreTime');
1448
- }
1449
- }
1450
- this.setKeypressTimer();
1451
- } else if (this.keypressTimeoutId) {
1452
- this.resetKeypressTimer();
1453
- }
1454
- this.lastKeyWasModified = event.shiftKey;
1455
- }
1456
- setKeypressTimer() {
1457
- if (this.keypressTimeoutId) {
1458
- window.clearTimeout(this.keypressTimeoutId);
1459
- }
1460
- this.keypressTimeoutId = window.setTimeout(this.resetKeypressTimer.bind(this), this.timeoutTime);
1461
- }
1462
- resetKeypressTimer() {
1463
- if (!this.$updateSpan) {
1464
- return;
1465
- }
1466
- if (this.keypressTimeoutId) {
1467
- window.clearTimeout(this.keypressTimeoutId);
1468
- this.keypressTimeoutId = null;
1469
- }
1470
- const $updateSpan = this.$updateSpan;
1471
- this.keypressCounter = 0;
1472
- $updateSpan.textContent = this.i18n.t('timedOut');
1473
- this.timeoutMessageId = window.setTimeout(() => {
1474
- $updateSpan.textContent = '';
1475
- }, this.timeoutTime);
1476
- this.updateIndicator();
1477
- }
1478
- resetPage() {
1479
- document.body.classList.remove('govuk-exit-this-page-hide-content');
1480
- if (this.$overlay) {
1481
- this.$overlay.remove();
1482
- this.$overlay = null;
1483
- }
1484
- if (this.$updateSpan) {
1485
- this.$updateSpan.setAttribute('role', 'status');
1486
- this.$updateSpan.textContent = '';
1487
- }
1488
- this.updateIndicator();
1489
- if (this.keypressTimeoutId) {
1490
- window.clearTimeout(this.keypressTimeoutId);
1491
- }
1492
- if (this.timeoutMessageId) {
1493
- window.clearTimeout(this.timeoutMessageId);
1494
- }
1495
- }
1496
- }
1497
-
1498
- /**
1499
- * Exit this Page config
1500
- *
1501
- * @see {@link ExitThisPage.defaults}
1502
- * @typedef {object} ExitThisPageConfig
1503
- * @property {ExitThisPageTranslations} [i18n=ExitThisPage.defaults.i18n] - Exit this page translations
1504
- */
1505
-
1506
- /**
1507
- * Exit this Page translations
1508
- *
1509
- * @see {@link ExitThisPage.defaults.i18n}
1510
- * @typedef {object} ExitThisPageTranslations
1511
- *
1512
- * Messages used by the component programatically inserted text, including
1513
- * overlay text and screen reader announcements.
1514
- * @property {string} [activated] - Screen reader announcement for when EtP
1515
- * keypress functionality has been successfully activated.
1516
- * @property {string} [timedOut] - Screen reader announcement for when the EtP
1517
- * keypress functionality has timed out.
1518
- * @property {string} [pressTwoMoreTimes] - Screen reader announcement informing
1519
- * the user they must press the activation key two more times.
1520
- * @property {string} [pressOneMoreTime] - Screen reader announcement informing
1521
- * the user they must press the activation key one more time.
1522
- */
1523
-
1524
- /**
1525
- * @typedef {import('../../common/index.mjs').Schema} Schema
1526
- */
1527
- ExitThisPage.moduleName = 'govuk-exit-this-page';
1528
- ExitThisPage.defaults = Object.freeze({
1529
- i18n: {
1530
- activated: 'Loading.',
1531
- timedOut: 'Exit this page expired.',
1532
- pressTwoMoreTimes: 'Shift, press 2 more times to exit.',
1533
- pressOneMoreTime: 'Shift, press 1 more time to exit.'
1534
- }
1535
- });
1536
- ExitThisPage.schema = Object.freeze({
1537
- properties: {
1538
- i18n: {
1539
- type: 'object'
1540
- }
1541
- }
1542
- });
1543
-
1544
- /**
1545
- * Header component
1546
- *
1547
- * @preserve
1548
- */
1549
- class Header extends GOVUKFrontendComponent {
1550
- /**
1551
- * Apply a matchMedia for desktop which will trigger a state sync if the
1552
- * browser viewport moves between states.
1553
- *
1554
- * @param {Element | null} $module - HTML element to use for header
1555
- */
1556
- constructor($module) {
1557
- super();
1558
- this.$module = void 0;
1559
- this.$menuButton = void 0;
1560
- this.$menu = void 0;
1561
- this.menuIsOpen = false;
1562
- this.mql = null;
1563
- if (!$module) {
1564
- throw new ElementError({
1565
- componentName: 'Header',
1566
- element: $module,
1567
- identifier: 'Root element (`$module`)'
1568
- });
1569
- }
1570
- this.$module = $module;
1571
- const $menuButton = $module.querySelector('.govuk-js-header-toggle');
1572
- if (!$menuButton) {
1573
- return this;
1574
- }
1575
- const menuId = $menuButton.getAttribute('aria-controls');
1576
- if (!menuId) {
1577
- throw new ElementError({
1578
- componentName: 'Header',
1579
- identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
1580
- });
1581
- }
1582
- const $menu = document.getElementById(menuId);
1583
- if (!$menu) {
1584
- throw new ElementError({
1585
- componentName: 'Header',
1586
- element: $menu,
1587
- identifier: `Navigation (\`<ul id="${menuId}">\`)`
1588
- });
1589
- }
1590
- this.$menu = $menu;
1591
- this.$menuButton = $menuButton;
1592
- this.setupResponsiveChecks();
1593
- this.$menuButton.addEventListener('click', () => this.handleMenuButtonClick());
1594
- }
1595
- setupResponsiveChecks() {
1596
- const breakpoint = getBreakpoint('desktop');
1597
- if (!breakpoint.value) {
1598
- throw new ElementError({
1599
- componentName: 'Header',
1600
- identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
1601
- });
1602
- }
1603
- this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
1604
- if ('addEventListener' in this.mql) {
1605
- this.mql.addEventListener('change', () => this.checkMode());
1606
- } else {
1607
- this.mql.addListener(() => this.checkMode());
1608
- }
1609
- this.checkMode();
1610
- }
1611
- checkMode() {
1612
- if (!this.mql || !this.$menu || !this.$menuButton) {
1613
- return;
1614
- }
1615
- if (this.mql.matches) {
1616
- this.$menu.removeAttribute('hidden');
1617
- this.$menuButton.setAttribute('hidden', '');
1618
- } else {
1619
- this.$menuButton.removeAttribute('hidden');
1620
- this.$menuButton.setAttribute('aria-expanded', this.menuIsOpen.toString());
1621
- if (this.menuIsOpen) {
1622
- this.$menu.removeAttribute('hidden');
1623
- } else {
1624
- this.$menu.setAttribute('hidden', '');
1625
- }
1626
- }
1627
- }
1628
- handleMenuButtonClick() {
1629
- this.menuIsOpen = !this.menuIsOpen;
1630
- this.checkMode();
1631
- }
1632
- }
1633
- Header.moduleName = 'govuk-header';
1634
-
1635
- /**
1636
- * Notification Banner component
1637
- *
1638
- * @preserve
1639
- */
1640
- class NotificationBanner extends GOVUKFrontendComponent {
1641
- /**
1642
- * @param {Element | null} $module - HTML element to use for notification banner
1643
- * @param {NotificationBannerConfig} [config] - Notification banner config
1644
- */
1645
- constructor($module, config = {}) {
1646
- super();
1647
- this.$module = void 0;
1648
- this.config = void 0;
1649
- if (!($module instanceof HTMLElement)) {
1650
- throw new ElementError({
1651
- componentName: 'Notification banner',
1652
- element: $module,
1653
- identifier: 'Root element (`$module`)'
1654
- });
1655
- }
1656
- this.$module = $module;
1657
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, $module.dataset));
1658
- if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
1659
- setFocus(this.$module);
1660
- }
1661
- }
1662
- }
1663
-
1664
- /**
1665
- * Notification banner config
1666
- *
1667
- * @typedef {object} NotificationBannerConfig
1668
- * @property {boolean} [disableAutoFocus=false] - If set to `true` the
1669
- * notification banner will not be focussed when the page loads. This only
1670
- * applies if the component has a `role` of `alert` – in other cases the
1671
- * component will not be focused on page load, regardless of this option.
1672
- */
1673
-
1674
- /**
1675
- * @typedef {import('../../common/index.mjs').Schema} Schema
1676
- */
1677
- NotificationBanner.moduleName = 'govuk-notification-banner';
1678
- NotificationBanner.defaults = Object.freeze({
1679
- disableAutoFocus: false
1680
- });
1681
- NotificationBanner.schema = Object.freeze({
1682
- properties: {
1683
- disableAutoFocus: {
1684
- type: 'boolean'
1685
- }
1686
- }
1687
- });
1688
-
1689
- /**
1690
- * Password input component
1691
- *
1692
- * @preserve
1693
- */
1694
- class PasswordInput extends GOVUKFrontendComponent {
1695
- /**
1696
- * @param {Element | null} $module - HTML element to use for password input
1697
- * @param {PasswordInputConfig} [config] - Password input config
1698
- */
1699
- constructor($module, config = {}) {
1700
- super();
1701
- this.$module = void 0;
1702
- this.config = void 0;
1703
- this.i18n = void 0;
1704
- this.$input = void 0;
1705
- this.$showHideButton = void 0;
1706
- this.$screenReaderStatusMessage = void 0;
1707
- if (!($module instanceof HTMLElement)) {
1708
- throw new ElementError({
1709
- componentName: 'Password input',
1710
- element: $module,
1711
- identifier: 'Root element (`$module`)'
1712
- });
1713
- }
1714
- const $input = $module.querySelector('.govuk-js-password-input-input');
1715
- if (!($input instanceof HTMLInputElement)) {
1716
- throw new ElementError({
1717
- componentName: 'Password input',
1718
- element: $input,
1719
- expectedType: 'HTMLInputElement',
1720
- identifier: 'Form field (`.govuk-js-password-input-input`)'
1721
- });
1722
- }
1723
- if ($input.type !== 'password') {
1724
- throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.');
1725
- }
1726
- const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle');
1727
- if (!($showHideButton instanceof HTMLButtonElement)) {
1728
- throw new ElementError({
1729
- componentName: 'Password input',
1730
- element: $showHideButton,
1731
- expectedType: 'HTMLButtonElement',
1732
- identifier: 'Button (`.govuk-js-password-input-toggle`)'
1733
- });
1734
- }
1735
- if ($showHideButton.type !== 'button') {
1736
- throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.');
1737
- }
1738
- this.$module = $module;
1739
- this.$input = $input;
1740
- this.$showHideButton = $showHideButton;
1741
- this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset));
1742
- this.i18n = new I18n(this.config.i18n, {
1743
- locale: closestAttributeValue($module, 'lang')
1744
- });
1745
- this.$showHideButton.removeAttribute('hidden');
1746
- const $screenReaderStatusMessage = document.createElement('div');
1747
- $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden';
1748
- $screenReaderStatusMessage.setAttribute('aria-live', 'polite');
1749
- this.$screenReaderStatusMessage = $screenReaderStatusMessage;
1750
- this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage);
1751
- this.$showHideButton.addEventListener('click', this.toggle.bind(this));
1752
- if (this.$input.form) {
1753
- this.$input.form.addEventListener('submit', () => this.hide());
1754
- }
1755
- window.addEventListener('pageshow', event => {
1756
- if (event.persisted && this.$input.type !== 'password') {
1757
- this.hide();
1758
- }
1759
- });
1760
- this.hide();
1761
- }
1762
- toggle(event) {
1763
- event.preventDefault();
1764
- if (this.$input.type === 'password') {
1765
- this.show();
1766
- return;
1767
- }
1768
- this.hide();
1769
- }
1770
- show() {
1771
- this.setType('text');
1772
- }
1773
- hide() {
1774
- this.setType('password');
1775
- }
1776
- setType(type) {
1777
- if (type === this.$input.type) {
1778
- return;
1779
- }
1780
- this.$input.setAttribute('type', type);
1781
- const isHidden = type === 'password';
1782
- const prefixButton = isHidden ? 'show' : 'hide';
1783
- const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown';
1784
- this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`);
1785
- this.$showHideButton.setAttribute('aria-label', this.i18n.t(`${prefixButton}PasswordAriaLabel`));
1786
- this.$screenReaderStatusMessage.innerText = this.i18n.t(`${prefixStatus}Announcement`);
1787
- }
1788
- }
1789
-
1790
- /**
1791
- * Password input config
1792
- *
1793
- * @typedef {object} PasswordInputConfig
1794
- * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations
1795
- */
1796
-
1797
- /**
1798
- * Password input translations
1799
- *
1800
- * @see {@link PasswordInput.defaults.i18n}
1801
- * @typedef {object} PasswordInputTranslations
1802
- *
1803
- * Messages displayed to the user indicating the state of the show/hide toggle.
1804
- * @property {string} [showPassword] - Visible text of the button when the
1805
- * password is currently hidden. Plain text only.
1806
- * @property {string} [hidePassword] - Visible text of the button when the
1807
- * password is currently visible. Plain text only.
1808
- * @property {string} [showPasswordAriaLabel] - aria-label of the button when
1809
- * the password is currently hidden. Plain text only.
1810
- * @property {string} [hidePasswordAriaLabel] - aria-label of the button when
1811
- * the password is currently visible. Plain text only.
1812
- * @property {string} [passwordShownAnnouncement] - Screen reader
1813
- * announcement to make when the password has just become visible.
1814
- * Plain text only.
1815
- * @property {string} [passwordHiddenAnnouncement] - Screen reader
1816
- * announcement to make when the password has just been hidden.
1817
- * Plain text only.
1818
- */
1819
-
1820
- /**
1821
- * @typedef {import('../../common/index.mjs').Schema} Schema
1822
- * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms
1823
- */
1824
- PasswordInput.moduleName = 'govuk-password-input';
1825
- PasswordInput.defaults = Object.freeze({
1826
- i18n: {
1827
- showPassword: 'Show',
1828
- hidePassword: 'Hide',
1829
- showPasswordAriaLabel: 'Show password',
1830
- hidePasswordAriaLabel: 'Hide password',
1831
- passwordShownAnnouncement: 'Your password is visible',
1832
- passwordHiddenAnnouncement: 'Your password is hidden'
1833
- }
1834
- });
1835
- PasswordInput.schema = Object.freeze({
1836
- properties: {
1837
- i18n: {
1838
- type: 'object'
1839
- }
1840
- }
1841
- });
1842
-
1843
- /**
1844
- * Radios component
1845
- *
1846
- * @preserve
1847
- */
1848
- class Radios extends GOVUKFrontendComponent {
1849
- /**
1850
- * Radios can be associated with a 'conditionally revealed' content block –
1851
- * for example, a radio for 'Phone' could reveal an additional form field for
1852
- * the user to enter their phone number.
1853
- *
1854
- * These associations are made using a `data-aria-controls` attribute, which
1855
- * is promoted to an aria-controls attribute during initialisation.
1856
- *
1857
- * We also need to restore the state of any conditional reveals on the page
1858
- * (for example if the user has navigated back), and set up event handlers to
1859
- * keep the reveal in sync with the radio state.
1860
- *
1861
- * @param {Element | null} $module - HTML element to use for radios
1862
- */
1863
- constructor($module) {
1864
- super();
1865
- this.$module = void 0;
1866
- this.$inputs = void 0;
1867
- if (!($module instanceof HTMLElement)) {
1868
- throw new ElementError({
1869
- componentName: 'Radios',
1870
- element: $module,
1871
- identifier: 'Root element (`$module`)'
1872
- });
1873
- }
1874
- const $inputs = $module.querySelectorAll('input[type="radio"]');
1875
- if (!$inputs.length) {
1876
- throw new ElementError({
1877
- componentName: 'Radios',
1878
- identifier: 'Form inputs (`<input type="radio">`)'
1879
- });
1880
- }
1881
- this.$module = $module;
1882
- this.$inputs = $inputs;
1883
- this.$inputs.forEach($input => {
1884
- const targetId = $input.getAttribute('data-aria-controls');
1885
- if (!targetId) {
1886
- return;
1887
- }
1888
- if (!document.getElementById(targetId)) {
1889
- throw new ElementError({
1890
- componentName: 'Radios',
1891
- identifier: `Conditional reveal (\`id="${targetId}"\`)`
1892
- });
1893
- }
1894
- $input.setAttribute('aria-controls', targetId);
1895
- $input.removeAttribute('data-aria-controls');
1896
- });
1897
- window.addEventListener('pageshow', () => this.syncAllConditionalReveals());
1898
- this.syncAllConditionalReveals();
1899
- this.$module.addEventListener('click', event => this.handleClick(event));
1900
- }
1901
- syncAllConditionalReveals() {
1902
- this.$inputs.forEach($input => this.syncConditionalRevealWithInputState($input));
1903
- }
1904
- syncConditionalRevealWithInputState($input) {
1905
- const targetId = $input.getAttribute('aria-controls');
1906
- if (!targetId) {
1907
- return;
1908
- }
1909
- const $target = document.getElementById(targetId);
1910
- if ($target != null && $target.classList.contains('govuk-radios__conditional')) {
1911
- const inputIsChecked = $input.checked;
1912
- $input.setAttribute('aria-expanded', inputIsChecked.toString());
1913
- $target.classList.toggle('govuk-radios__conditional--hidden', !inputIsChecked);
1914
- }
1915
- }
1916
- handleClick(event) {
1917
- const $clickedInput = event.target;
1918
- if (!($clickedInput instanceof HTMLInputElement) || $clickedInput.type !== 'radio') {
1919
- return;
1920
- }
1921
- const $allInputs = document.querySelectorAll('input[type="radio"][aria-controls]');
1922
- const $clickedInputForm = $clickedInput.form;
1923
- const $clickedInputName = $clickedInput.name;
1924
- $allInputs.forEach($input => {
1925
- const hasSameFormOwner = $input.form === $clickedInputForm;
1926
- const hasSameName = $input.name === $clickedInputName;
1927
- if (hasSameName && hasSameFormOwner) {
1928
- this.syncConditionalRevealWithInputState($input);
1929
- }
1930
- });
1931
- }
1932
- }
1933
- Radios.moduleName = 'govuk-radios';
1934
-
1935
- /**
1936
- * Skip link component
1937
- *
1938
- * @preserve
1939
- */
1940
- class SkipLink extends GOVUKFrontendComponent {
1941
- /**
1942
- * @param {Element | null} $module - HTML element to use for skip link
1943
- * @throws {ElementError} when $module is not set or the wrong type
1944
- * @throws {ElementError} when $module.hash does not contain a hash
1945
- * @throws {ElementError} when the linked element is missing or the wrong type
1946
- */
1947
- constructor($module) {
1948
- var _this$$module$getAttr;
1949
- super();
1950
- this.$module = void 0;
1951
- if (!($module instanceof HTMLAnchorElement)) {
1952
- throw new ElementError({
1953
- componentName: 'Skip link',
1954
- element: $module,
1955
- expectedType: 'HTMLAnchorElement',
1956
- identifier: 'Root element (`$module`)'
1957
- });
1958
- }
1959
- this.$module = $module;
1960
- const hash = this.$module.hash;
1961
- const href = (_this$$module$getAttr = this.$module.getAttribute('href')) != null ? _this$$module$getAttr : '';
1962
- let url;
1963
- try {
1964
- url = new window.URL(this.$module.href);
1965
- } catch (error) {
1966
- throw new ElementError(`Skip link: Target link (\`href="${href}"\`) is invalid`);
1967
- }
1968
- if (url.origin !== window.location.origin || url.pathname !== window.location.pathname) {
1969
- return;
1970
- }
1971
- const linkedElementId = getFragmentFromUrl(hash);
1972
- if (!linkedElementId) {
1973
- throw new ElementError(`Skip link: Target link (\`href="${href}"\`) has no hash fragment`);
1974
- }
1975
- const $linkedElement = document.getElementById(linkedElementId);
1976
- if (!$linkedElement) {
1977
- throw new ElementError({
1978
- componentName: 'Skip link',
1979
- element: $linkedElement,
1980
- identifier: `Target content (\`id="${linkedElementId}"\`)`
1981
- });
1982
- }
1983
- this.$module.addEventListener('click', () => setFocus($linkedElement, {
1984
- onBeforeFocus() {
1985
- $linkedElement.classList.add('govuk-skip-link-focused-element');
1986
- },
1987
- onBlur() {
1988
- $linkedElement.classList.remove('govuk-skip-link-focused-element');
1989
- }
1990
- }));
1991
- }
1992
- }
1993
- SkipLink.moduleName = 'govuk-skip-link';
1994
-
1995
- /**
1996
- * Tabs component
1997
- *
1998
- * @preserve
1999
- */
2000
- class Tabs extends GOVUKFrontendComponent {
2001
- /**
2002
- * @param {Element | null} $module - HTML element to use for tabs
2003
- */
2004
- constructor($module) {
2005
- super();
2006
- this.$module = void 0;
2007
- this.$tabs = void 0;
2008
- this.$tabList = void 0;
2009
- this.$tabListItems = void 0;
2010
- this.jsHiddenClass = 'govuk-tabs__panel--hidden';
2011
- this.changingHash = false;
2012
- this.boundTabClick = void 0;
2013
- this.boundTabKeydown = void 0;
2014
- this.boundOnHashChange = void 0;
2015
- this.mql = null;
2016
- if (!$module) {
2017
- throw new ElementError({
2018
- componentName: 'Tabs',
2019
- element: $module,
2020
- identifier: 'Root element (`$module`)'
2021
- });
2022
- }
2023
- const $tabs = $module.querySelectorAll('a.govuk-tabs__tab');
2024
- if (!$tabs.length) {
2025
- throw new ElementError({
2026
- componentName: 'Tabs',
2027
- identifier: 'Links (`<a class="govuk-tabs__tab">`)'
2028
- });
2029
- }
2030
- this.$module = $module;
2031
- this.$tabs = $tabs;
2032
- this.boundTabClick = this.onTabClick.bind(this);
2033
- this.boundTabKeydown = this.onTabKeydown.bind(this);
2034
- this.boundOnHashChange = this.onHashChange.bind(this);
2035
- const $tabList = this.$module.querySelector('.govuk-tabs__list');
2036
- const $tabListItems = this.$module.querySelectorAll('li.govuk-tabs__list-item');
2037
- if (!$tabList) {
2038
- throw new ElementError({
2039
- componentName: 'Tabs',
2040
- identifier: 'List (`<ul class="govuk-tabs__list">`)'
2041
- });
2042
- }
2043
- if (!$tabListItems.length) {
2044
- throw new ElementError({
2045
- componentName: 'Tabs',
2046
- identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
2047
- });
2048
- }
2049
- this.$tabList = $tabList;
2050
- this.$tabListItems = $tabListItems;
2051
- this.setupResponsiveChecks();
2052
- }
2053
- setupResponsiveChecks() {
2054
- const breakpoint = getBreakpoint('tablet');
2055
- if (!breakpoint.value) {
2056
- throw new ElementError({
2057
- componentName: 'Tabs',
2058
- identifier: `CSS custom property (\`${breakpoint.property}\`) on pseudo-class \`:root\``
2059
- });
2060
- }
2061
- this.mql = window.matchMedia(`(min-width: ${breakpoint.value})`);
2062
- if ('addEventListener' in this.mql) {
2063
- this.mql.addEventListener('change', () => this.checkMode());
2064
- } else {
2065
- this.mql.addListener(() => this.checkMode());
2066
- }
2067
- this.checkMode();
2068
- }
2069
- checkMode() {
2070
- var _this$mql;
2071
- if ((_this$mql = this.mql) != null && _this$mql.matches) {
2072
- this.setup();
2073
- } else {
2074
- this.teardown();
2075
- }
2076
- }
2077
- setup() {
2078
- var _this$getTab;
2079
- this.$tabList.setAttribute('role', 'tablist');
2080
- this.$tabListItems.forEach($item => {
2081
- $item.setAttribute('role', 'presentation');
2082
- });
2083
- this.$tabs.forEach($tab => {
2084
- this.setAttributes($tab);
2085
- $tab.addEventListener('click', this.boundTabClick, true);
2086
- $tab.addEventListener('keydown', this.boundTabKeydown, true);
2087
- this.hideTab($tab);
2088
- });
2089
- const $activeTab = (_this$getTab = this.getTab(window.location.hash)) != null ? _this$getTab : this.$tabs[0];
2090
- this.showTab($activeTab);
2091
- window.addEventListener('hashchange', this.boundOnHashChange, true);
2092
- }
2093
- teardown() {
2094
- this.$tabList.removeAttribute('role');
2095
- this.$tabListItems.forEach($item => {
2096
- $item.removeAttribute('role');
2097
- });
2098
- this.$tabs.forEach($tab => {
2099
- $tab.removeEventListener('click', this.boundTabClick, true);
2100
- $tab.removeEventListener('keydown', this.boundTabKeydown, true);
2101
- this.unsetAttributes($tab);
2102
- });
2103
- window.removeEventListener('hashchange', this.boundOnHashChange, true);
2104
- }
2105
- onHashChange() {
2106
- const hash = window.location.hash;
2107
- const $tabWithHash = this.getTab(hash);
2108
- if (!$tabWithHash) {
2109
- return;
2110
- }
2111
- if (this.changingHash) {
2112
- this.changingHash = false;
2113
- return;
2114
- }
2115
- const $previousTab = this.getCurrentTab();
2116
- if (!$previousTab) {
2117
- return;
2118
- }
2119
- this.hideTab($previousTab);
2120
- this.showTab($tabWithHash);
2121
- $tabWithHash.focus();
2122
- }
2123
- hideTab($tab) {
2124
- this.unhighlightTab($tab);
2125
- this.hidePanel($tab);
2126
- }
2127
- showTab($tab) {
2128
- this.highlightTab($tab);
2129
- this.showPanel($tab);
2130
- }
2131
- getTab(hash) {
2132
- return this.$module.querySelector(`a.govuk-tabs__tab[href="${hash}"]`);
2133
- }
2134
- setAttributes($tab) {
2135
- const panelId = getFragmentFromUrl($tab.href);
2136
- if (!panelId) {
2137
- return;
2138
- }
2139
- $tab.setAttribute('id', `tab_${panelId}`);
2140
- $tab.setAttribute('role', 'tab');
2141
- $tab.setAttribute('aria-controls', panelId);
2142
- $tab.setAttribute('aria-selected', 'false');
2143
- $tab.setAttribute('tabindex', '-1');
2144
- const $panel = this.getPanel($tab);
2145
- if (!$panel) {
2146
- return;
2147
- }
2148
- $panel.setAttribute('role', 'tabpanel');
2149
- $panel.setAttribute('aria-labelledby', $tab.id);
2150
- $panel.classList.add(this.jsHiddenClass);
2151
- }
2152
- unsetAttributes($tab) {
2153
- $tab.removeAttribute('id');
2154
- $tab.removeAttribute('role');
2155
- $tab.removeAttribute('aria-controls');
2156
- $tab.removeAttribute('aria-selected');
2157
- $tab.removeAttribute('tabindex');
2158
- const $panel = this.getPanel($tab);
2159
- if (!$panel) {
2160
- return;
2161
- }
2162
- $panel.removeAttribute('role');
2163
- $panel.removeAttribute('aria-labelledby');
2164
- $panel.classList.remove(this.jsHiddenClass);
2165
- }
2166
- onTabClick(event) {
2167
- const $currentTab = this.getCurrentTab();
2168
- const $nextTab = event.currentTarget;
2169
- if (!$currentTab || !($nextTab instanceof HTMLAnchorElement)) {
2170
- return;
2171
- }
2172
- event.preventDefault();
2173
- this.hideTab($currentTab);
2174
- this.showTab($nextTab);
2175
- this.createHistoryEntry($nextTab);
2176
- }
2177
- createHistoryEntry($tab) {
2178
- const $panel = this.getPanel($tab);
2179
- if (!$panel) {
2180
- return;
2181
- }
2182
- const panelId = $panel.id;
2183
- $panel.id = '';
2184
- this.changingHash = true;
2185
- window.location.hash = panelId;
2186
- $panel.id = panelId;
2187
- }
2188
- onTabKeydown(event) {
2189
- switch (event.key) {
2190
- case 'ArrowLeft':
2191
- case 'ArrowUp':
2192
- case 'Left':
2193
- case 'Up':
2194
- this.activatePreviousTab();
2195
- event.preventDefault();
2196
- break;
2197
- case 'ArrowRight':
2198
- case 'ArrowDown':
2199
- case 'Right':
2200
- case 'Down':
2201
- this.activateNextTab();
2202
- event.preventDefault();
2203
- break;
2204
- }
2205
- }
2206
- activateNextTab() {
2207
- const $currentTab = this.getCurrentTab();
2208
- if (!($currentTab != null && $currentTab.parentElement)) {
2209
- return;
2210
- }
2211
- const $nextTabListItem = $currentTab.parentElement.nextElementSibling;
2212
- if (!$nextTabListItem) {
2213
- return;
2214
- }
2215
- const $nextTab = $nextTabListItem.querySelector('a.govuk-tabs__tab');
2216
- if (!$nextTab) {
2217
- return;
2218
- }
2219
- this.hideTab($currentTab);
2220
- this.showTab($nextTab);
2221
- $nextTab.focus();
2222
- this.createHistoryEntry($nextTab);
2223
- }
2224
- activatePreviousTab() {
2225
- const $currentTab = this.getCurrentTab();
2226
- if (!($currentTab != null && $currentTab.parentElement)) {
2227
- return;
2228
- }
2229
- const $previousTabListItem = $currentTab.parentElement.previousElementSibling;
2230
- if (!$previousTabListItem) {
2231
- return;
2232
- }
2233
- const $previousTab = $previousTabListItem.querySelector('a.govuk-tabs__tab');
2234
- if (!$previousTab) {
2235
- return;
2236
- }
2237
- this.hideTab($currentTab);
2238
- this.showTab($previousTab);
2239
- $previousTab.focus();
2240
- this.createHistoryEntry($previousTab);
2241
- }
2242
- getPanel($tab) {
2243
- const panelId = getFragmentFromUrl($tab.href);
2244
- if (!panelId) {
2245
- return null;
2246
- }
2247
- return this.$module.querySelector(`#${panelId}`);
2248
- }
2249
- showPanel($tab) {
2250
- const $panel = this.getPanel($tab);
2251
- if (!$panel) {
2252
- return;
2253
- }
2254
- $panel.classList.remove(this.jsHiddenClass);
2255
- }
2256
- hidePanel($tab) {
2257
- const $panel = this.getPanel($tab);
2258
- if (!$panel) {
2259
- return;
2260
- }
2261
- $panel.classList.add(this.jsHiddenClass);
2262
- }
2263
- unhighlightTab($tab) {
2264
- if (!$tab.parentElement) {
2265
- return;
2266
- }
2267
- $tab.setAttribute('aria-selected', 'false');
2268
- $tab.parentElement.classList.remove('govuk-tabs__list-item--selected');
2269
- $tab.setAttribute('tabindex', '-1');
2270
- }
2271
- highlightTab($tab) {
2272
- if (!$tab.parentElement) {
2273
- return;
2274
- }
2275
- $tab.setAttribute('aria-selected', 'true');
2276
- $tab.parentElement.classList.add('govuk-tabs__list-item--selected');
2277
- $tab.setAttribute('tabindex', '0');
2278
- }
2279
- getCurrentTab() {
2280
- return this.$module.querySelector('.govuk-tabs__list-item--selected a.govuk-tabs__tab');
2281
- }
2282
- }
2283
- Tabs.moduleName = 'govuk-tabs';
2284
-
2285
- function initAll(config) {
2286
- let _config$scope;
2287
- config = typeof config !== 'undefined' ? config : {};
2288
- if (!isSupported()) {
2289
- console.log(new SupportError());
2290
- return;
2291
- }
2292
- const components = [[Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [Radios], [PasswordInput, config.passwordInput]];
2293
- const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
2294
- components.forEach(([Component, config]) => {
2295
- const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
2296
- $elements.forEach($element => {
2297
- try {
2298
- 'defaults' in Component ? new Component($element, config) : new Component($element);
2299
- } catch (error) {
2300
- console.log(error);
2301
- }
2302
- });
2303
- });
2304
- }
2305
-
2306
- export { Button, CharacterCount, Checkboxes, ErrorSummary, PasswordInput, Radios, initAll };