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