@govuk-one-login/frontend-ui 4.1.4 → 4.2.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.
- package/build/all.css +1 -0
- package/build/cjs/backend/index.cjs +257 -0
- package/build/cjs/backend/index.d.cts +39 -0
- package/build/cjs/backend/index.d.ts +39 -0
- package/build/cjs/backend/index.d.ts.map +1 -0
- package/build/cjs/backend/lib/helmet.d.ts +31 -0
- package/build/cjs/backend/lib/helmet.d.ts.map +1 -0
- package/build/cjs/backend/lib/index.d.ts +4 -0
- package/build/cjs/backend/lib/index.d.ts.map +1 -0
- package/build/cjs/backend/lib/locals.d.ts +11 -0
- package/build/cjs/backend/lib/locals.d.ts.map +1 -0
- package/build/cjs/backend/lib/settings.d.ts +21 -0
- package/build/cjs/backend/lib/settings.d.ts.map +1 -0
- package/build/cjs/backend/utils/logger.d.ts +9 -0
- package/build/cjs/backend/utils/logger.d.ts.map +1 -0
- package/build/cjs/frontend/index.cjs +443 -0
- package/build/cjs/frontend/index.d.cts +4 -0
- package/build/cjs/frontend/index.d.ts +4 -0
- package/build/cjs/frontend/index.d.ts.map +1 -0
- package/build/cjs/frontend/progress-button/progress-button.d.ts +2 -0
- package/build/cjs/frontend/progress-button/progress-button.d.ts.map +1 -0
- package/build/cjs/frontend/spinner/__tests__/spinner.test.d.ts +2 -0
- package/build/cjs/frontend/spinner/__tests__/spinner.test.d.ts.map +1 -0
- package/build/cjs/frontend/spinner/spinner.d.ts +64 -0
- package/build/cjs/frontend/spinner/spinner.d.ts.map +1 -0
- package/build/cjs/frontend/utils/types.d.ts +108 -0
- package/build/cjs/frontend/utils/types.d.ts.map +1 -0
- package/build/components/_all.scss +6 -0
- package/build/components/bases/auth/auth-base.njk +136 -0
- package/build/components/bases/home/home-base.njk +133 -0
- package/build/components/bases/identity/identity-base-form.njk +123 -0
- package/build/components/bases/identity/identity-base-page.njk +123 -0
- package/build/components/bases/ipv-core/ipv-core-base.njk +172 -0
- package/build/components/bases/mobile/mobile-base.njk +195 -0
- package/build/components/cookie-banner/cookie-banner.yaml +6 -0
- package/build/components/cookie-banner/macro.njk +1 -0
- package/build/components/cookie-banner/template.njk +106 -0
- package/build/components/footer/_index.scss +28 -0
- package/build/components/footer/footer.yaml +5 -0
- package/build/components/footer/macro.njk +1 -0
- package/build/components/footer/template.njk +12 -0
- package/build/components/header/README.md +34 -0
- package/build/components/header/_index.scss +102 -0
- package/build/components/header/header.yaml +16 -0
- package/build/components/header/macro.njk +3 -0
- package/build/components/header/template.njk +39 -0
- package/build/components/language-select/_index.scss +35 -0
- package/build/components/language-select/language-select.yaml +43 -0
- package/build/components/language-select/macro.njk +2 -0
- package/build/components/language-select/template.njk +38 -0
- package/build/components/macros/logo.njk +75 -0
- package/build/components/phase-banner/_index.scss +28 -0
- package/build/components/phase-banner/macro.njk +3 -0
- package/build/components/phase-banner/phase-banner.yaml +17 -0
- package/build/components/phase-banner/tag/macro.njk +3 -0
- package/build/components/phase-banner/tag/template.njk +3 -0
- package/build/components/phase-banner/template.njk +26 -0
- package/build/components/progress-button/_index.scss +51 -0
- package/build/components/progress-button/macro.njk +2 -0
- package/build/components/progress-button/progress-button.yaml +13 -0
- package/build/components/progress-button/template.njk +112 -0
- package/build/components/skip-link/README.md +10 -0
- package/build/components/skip-link/macro.njk +3 -0
- package/build/components/skip-link/skip-link.yaml +5 -0
- package/build/components/skip-link/template.njk +14 -0
- package/build/components/spinner/README.md +130 -0
- package/build/components/spinner/_index.scss +55 -0
- package/build/components/spinner/macro.njk +3 -0
- package/build/components/spinner/template.njk +18 -0
- package/build/esm/backend/index.d.ts +39 -0
- package/build/esm/backend/index.d.ts.map +1 -0
- package/build/esm/backend/index.js +247 -0
- package/build/esm/backend/lib/helmet.d.ts +31 -0
- package/build/esm/backend/lib/helmet.d.ts.map +1 -0
- package/build/esm/backend/lib/index.d.ts +4 -0
- package/build/esm/backend/lib/index.d.ts.map +1 -0
- package/build/esm/backend/lib/locals.d.ts +11 -0
- package/build/esm/backend/lib/locals.d.ts.map +1 -0
- package/build/esm/backend/lib/settings.d.ts +21 -0
- package/build/esm/backend/lib/settings.d.ts.map +1 -0
- package/build/esm/backend/utils/logger.d.ts +9 -0
- package/build/esm/backend/utils/logger.d.ts.map +1 -0
- package/build/esm/frontend/index.d.ts +4 -0
- package/build/esm/frontend/index.d.ts.map +1 -0
- package/build/esm/frontend/index.js +440 -0
- package/build/esm/frontend/progress-button/progress-button.d.ts +2 -0
- package/build/esm/frontend/progress-button/progress-button.d.ts.map +1 -0
- package/build/esm/frontend/spinner/__tests__/spinner.test.d.ts +2 -0
- package/build/esm/frontend/spinner/__tests__/spinner.test.d.ts.map +1 -0
- package/build/esm/frontend/spinner/spinner.d.ts +64 -0
- package/build/esm/frontend/spinner/spinner.d.ts.map +1 -0
- package/build/esm/frontend/utils/types.d.ts +108 -0
- package/build/esm/frontend/utils/types.d.ts.map +1 -0
- package/package.json +1 -2
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
async function useSpinner(containerId, pollingFunction, successFunction, errorFunction) {
|
|
4
|
+
const element = document.getElementById(containerId);
|
|
5
|
+
if (element && element instanceof HTMLDivElement) {
|
|
6
|
+
let spinner;
|
|
7
|
+
try {
|
|
8
|
+
spinner = new Spinner(element, pollingFunction, successFunction, errorFunction);
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
const errorText = document.createElement("p");
|
|
12
|
+
errorText.textContent = "Error configuring spinner: " + e;
|
|
13
|
+
element.replaceChildren(errorText);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await spinner.init();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.error(`Attempting to initiate a spinner on a page with no '#${containerId}' div element.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.PollResult = void 0;
|
|
23
|
+
(function (PollResult) {
|
|
24
|
+
PollResult[PollResult["Success"] = 0] = "Success";
|
|
25
|
+
PollResult[PollResult["Failure"] = 1] = "Failure";
|
|
26
|
+
PollResult[PollResult["Pending"] = 2] = "Pending";
|
|
27
|
+
PollResult[PollResult["Backoff"] = 3] = "Backoff";
|
|
28
|
+
})(exports.PollResult || (exports.PollResult = {}));
|
|
29
|
+
var SpinnerState;
|
|
30
|
+
(function (SpinnerState) {
|
|
31
|
+
SpinnerState[SpinnerState["Waiting"] = 0] = "Waiting";
|
|
32
|
+
SpinnerState[SpinnerState["LongWaiting"] = 1] = "LongWaiting";
|
|
33
|
+
SpinnerState[SpinnerState["Error"] = 2] = "Error";
|
|
34
|
+
SpinnerState[SpinnerState["Complete"] = 3] = "Complete";
|
|
35
|
+
})(SpinnerState || (SpinnerState = {}));
|
|
36
|
+
class Spinner {
|
|
37
|
+
constructor(domContainer, pollingFunction, onSuccess, onError) {
|
|
38
|
+
this.state = SpinnerState.Waiting;
|
|
39
|
+
this.backOffCount = 0;
|
|
40
|
+
this.handleAbort = () => {
|
|
41
|
+
this.abortController.abort();
|
|
42
|
+
};
|
|
43
|
+
this.initTimer = (initTime) => {
|
|
44
|
+
this.updateDomTimer = setInterval(() => {
|
|
45
|
+
this.updateStateAccordingToTimeElapsed(initTime);
|
|
46
|
+
this.updateDom();
|
|
47
|
+
}, this.config.msBetweenDomUpdate);
|
|
48
|
+
};
|
|
49
|
+
this.reflectSuccess = () => {
|
|
50
|
+
this.state = SpinnerState.Complete;
|
|
51
|
+
sessionStorage.removeItem("spinnerInitTime");
|
|
52
|
+
};
|
|
53
|
+
this.reflectError = () => {
|
|
54
|
+
this.state = SpinnerState.Error;
|
|
55
|
+
sessionStorage.removeItem("spinnerInitTime");
|
|
56
|
+
this.abortController.abort();
|
|
57
|
+
};
|
|
58
|
+
this.updateStateAccordingToTimeElapsed = (initTime) => {
|
|
59
|
+
const elapsedMilliseconds = Date.now() - initTime;
|
|
60
|
+
if (this.hasCompleted()) {
|
|
61
|
+
// If we've already finished waiting then there's no need to update again.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (elapsedMilliseconds >= this.config.msBeforeAbort) {
|
|
65
|
+
this.reflectError();
|
|
66
|
+
}
|
|
67
|
+
else if (elapsedMilliseconds >= this.config.msBeforeInformingOfLongWait) {
|
|
68
|
+
this.reflectLongWait();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
this.updateDom = () => {
|
|
72
|
+
if (this.hasCompleted()) {
|
|
73
|
+
// We've reached an end state so stop updating the DOM after this
|
|
74
|
+
clearInterval(this.updateDomTimer);
|
|
75
|
+
}
|
|
76
|
+
if (this.displayState === this.state) {
|
|
77
|
+
// No need to update anything
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.displayState = this.state;
|
|
81
|
+
const newElementsToDisplay = [];
|
|
82
|
+
switch (this.displayState) {
|
|
83
|
+
case SpinnerState.Waiting:
|
|
84
|
+
newElementsToDisplay.push(this.createSpinnerElement(""));
|
|
85
|
+
this.cloneAndAddIfExists(newElementsToDisplay, this.waitContent);
|
|
86
|
+
break;
|
|
87
|
+
case SpinnerState.LongWaiting:
|
|
88
|
+
newElementsToDisplay.push(this.createSpinnerElement(""));
|
|
89
|
+
this.cloneAndAddIfExists(newElementsToDisplay, this.longWaitContent);
|
|
90
|
+
break;
|
|
91
|
+
case SpinnerState.Complete:
|
|
92
|
+
newElementsToDisplay.push(this.createSpinnerElement("spinner__finished"));
|
|
93
|
+
this.cloneAndAddIfExists(newElementsToDisplay, this.successContent);
|
|
94
|
+
break;
|
|
95
|
+
case SpinnerState.Error:
|
|
96
|
+
if (!this.config.hideSpinnerOnError) {
|
|
97
|
+
newElementsToDisplay.push(this.createSpinnerElement("spinner__finished"));
|
|
98
|
+
}
|
|
99
|
+
this.cloneAndAddIfExists(newElementsToDisplay, this.errorContent);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
this.visibleElementsContainer.replaceChildren(...newElementsToDisplay);
|
|
103
|
+
if (this.displayState === SpinnerState.Complete) {
|
|
104
|
+
if (this.onSuccess) {
|
|
105
|
+
this.onSuccess();
|
|
106
|
+
}
|
|
107
|
+
this.updateAriaAlert(this.config.ariaAlertCompletionText);
|
|
108
|
+
}
|
|
109
|
+
if (this.displayState === SpinnerState.Error && !!this.onError) {
|
|
110
|
+
this.onError();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
this.container = domContainer;
|
|
114
|
+
this.pollingFunction = pollingFunction;
|
|
115
|
+
if (!pollingFunction) {
|
|
116
|
+
throw new Error("Polling function must be provided");
|
|
117
|
+
}
|
|
118
|
+
this.onSuccess = onSuccess;
|
|
119
|
+
this.onError = onError;
|
|
120
|
+
this.noJsContent = this.getElementOrThrow("no-js-content");
|
|
121
|
+
this.noJsContent.style.display = "none";
|
|
122
|
+
this.waitContent =
|
|
123
|
+
this.container.querySelector("#wait-content") || undefined;
|
|
124
|
+
this.longWaitContent =
|
|
125
|
+
this.container.querySelector("#long-wait-content") || undefined;
|
|
126
|
+
this.successContent =
|
|
127
|
+
this.container.querySelector("#success-content") || undefined;
|
|
128
|
+
this.errorContent =
|
|
129
|
+
this.container.querySelector("#error-content") || undefined;
|
|
130
|
+
if (!this.successContent && !onSuccess) {
|
|
131
|
+
throw new Error("One of success-content or successFunction must be provided");
|
|
132
|
+
}
|
|
133
|
+
if (!this.errorContent && !onError) {
|
|
134
|
+
throw new Error("One of error-content or errorFunction must be provided");
|
|
135
|
+
}
|
|
136
|
+
this.config = this.getConfig(this.container);
|
|
137
|
+
this.visibleElementsContainer = document.createElement("div");
|
|
138
|
+
this.container.appendChild(this.visibleElementsContainer);
|
|
139
|
+
this.ariaLiveContainer = this.createAriaLiveContainer();
|
|
140
|
+
this.container.appendChild(this.ariaLiveContainer);
|
|
141
|
+
this.abortController = this.createAbortController();
|
|
142
|
+
}
|
|
143
|
+
async init() {
|
|
144
|
+
const initTime = this.getInitTime();
|
|
145
|
+
this.updateStateAccordingToTimeElapsed(initTime);
|
|
146
|
+
this.updateDom();
|
|
147
|
+
this.initTimer(initTime);
|
|
148
|
+
await this.callPollingFunction(initTime);
|
|
149
|
+
}
|
|
150
|
+
getElementOrThrow(elementId) {
|
|
151
|
+
const element = this.container.querySelector(`#${elementId}`);
|
|
152
|
+
if (element === null || !(element instanceof HTMLElement)) {
|
|
153
|
+
throw new Error(`HTML Element with id ${elementId} must be provided`);
|
|
154
|
+
}
|
|
155
|
+
return element;
|
|
156
|
+
}
|
|
157
|
+
getConfig(element) {
|
|
158
|
+
return {
|
|
159
|
+
msBeforeInformingOfLongWait: element.dataset.msBeforeInformingOfLongWait
|
|
160
|
+
? parseInt(element.dataset.msBeforeInformingOfLongWait)
|
|
161
|
+
: 5000,
|
|
162
|
+
msBeforeAbort: element.dataset.msBeforeAbort
|
|
163
|
+
? parseInt(element.dataset.msBeforeAbort)
|
|
164
|
+
: 30000,
|
|
165
|
+
msBetweenRequests: element.dataset.msBetweenRequests
|
|
166
|
+
? parseInt(element.dataset.msBetweenRequests)
|
|
167
|
+
: 2000,
|
|
168
|
+
msBetweenDomUpdate: element.dataset.msBetweenDomUpdate
|
|
169
|
+
? parseInt(element.dataset.msBetweenDomUpdate)
|
|
170
|
+
: 1000,
|
|
171
|
+
ariaAlertCompletionText: element.dataset.ariaAlertCompletionText
|
|
172
|
+
? element.dataset.ariaAlertCompletionText
|
|
173
|
+
: undefined,
|
|
174
|
+
hideSpinnerOnError: element.dataset.hideSpinnerOnError
|
|
175
|
+
? (element.dataset.hideSpinnerOnError === 'true')
|
|
176
|
+
: false,
|
|
177
|
+
maxBackoffTries: element.dataset.maxBackoffTries
|
|
178
|
+
? parseInt(element.dataset.maxBackoffTries)
|
|
179
|
+
: 3,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
createAbortController() {
|
|
183
|
+
const abortController = new AbortController();
|
|
184
|
+
window.removeEventListener("beforeunload", this.handleAbort);
|
|
185
|
+
window.addEventListener("beforeunload", this.handleAbort);
|
|
186
|
+
return abortController;
|
|
187
|
+
}
|
|
188
|
+
getInitTime() {
|
|
189
|
+
const storedSpinnerInitTime = sessionStorage.getItem("spinnerInitTime");
|
|
190
|
+
let spinnerInitTime;
|
|
191
|
+
if (storedSpinnerInitTime === null) {
|
|
192
|
+
spinnerInitTime = Date.now();
|
|
193
|
+
sessionStorage.setItem("spinnerInitTime", spinnerInitTime.toString());
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
spinnerInitTime = parseInt(storedSpinnerInitTime, 10);
|
|
197
|
+
}
|
|
198
|
+
return spinnerInitTime;
|
|
199
|
+
}
|
|
200
|
+
hasCompleted() {
|
|
201
|
+
return (this.state === SpinnerState.Error || this.state === SpinnerState.Complete);
|
|
202
|
+
}
|
|
203
|
+
reflectLongWait() {
|
|
204
|
+
if (!this.hasCompleted()) {
|
|
205
|
+
this.state = SpinnerState.LongWaiting;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
createSpinnerElement(spinnerStateClass) {
|
|
209
|
+
const spinner = document.createElement("div");
|
|
210
|
+
spinner.id = "spinner";
|
|
211
|
+
spinner.classList.add("spinner", "centre");
|
|
212
|
+
if (spinnerStateClass) {
|
|
213
|
+
spinner.classList.add(spinnerStateClass);
|
|
214
|
+
}
|
|
215
|
+
return spinner;
|
|
216
|
+
}
|
|
217
|
+
cloneAndAddIfExists(list, element) {
|
|
218
|
+
if (element) {
|
|
219
|
+
const cloned = element.cloneNode(true);
|
|
220
|
+
cloned.style.display = "";
|
|
221
|
+
cloned.removeAttribute("id");
|
|
222
|
+
list.push(cloned);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async callPollingFunction(initTime) {
|
|
226
|
+
if (Date.now() - initTime >= this.config.msBeforeAbort) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await this.pollingFunction(this.abortController.signal)
|
|
230
|
+
.then((response) => {
|
|
231
|
+
if (response === exports.PollResult.Success) {
|
|
232
|
+
this.reflectSuccess();
|
|
233
|
+
}
|
|
234
|
+
else if (response === exports.PollResult.Failure) {
|
|
235
|
+
this.reflectError();
|
|
236
|
+
}
|
|
237
|
+
else if (!this.hasCompleted()) {
|
|
238
|
+
let timeToNextPoll = this.config.msBetweenRequests;
|
|
239
|
+
if (response === exports.PollResult.Backoff) {
|
|
240
|
+
this.backOffCount++;
|
|
241
|
+
if (this.backOffCount > this.config.maxBackoffTries) {
|
|
242
|
+
this.reflectError();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
timeToNextPoll = this.calculateBackoffTime(this.backOffCount);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
this.backOffCount = 0;
|
|
249
|
+
}
|
|
250
|
+
setTimeout(async () => {
|
|
251
|
+
if (Date.now() - initTime >= this.config.msBeforeAbort) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await this.callPollingFunction(initTime);
|
|
255
|
+
}, timeToNextPoll);
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
.catch((error) => {
|
|
259
|
+
if (error.name !== "AbortError") {
|
|
260
|
+
console.error("Error in polling function: ", error);
|
|
261
|
+
this.reflectError();
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
.finally(() => {
|
|
265
|
+
this.updateDom();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
calculateBackoffTime(backOffCount) {
|
|
269
|
+
const extraDelay = Math.pow(2, backOffCount - 2) * this.config.msBetweenRequests;
|
|
270
|
+
return this.config.msBetweenRequests + extraDelay;
|
|
271
|
+
}
|
|
272
|
+
createAriaLiveContainer() {
|
|
273
|
+
// For the Aria alert to work reliably we need to create its container once and then update the contents
|
|
274
|
+
// https://tetralogical.com/blog/2024/05/01/why-are-my-live-regions-not-working/
|
|
275
|
+
const container = document.createElement("div");
|
|
276
|
+
container.setAttribute("aria-live", "assertive");
|
|
277
|
+
container.classList.add("govuk-visually-hidden");
|
|
278
|
+
return container;
|
|
279
|
+
}
|
|
280
|
+
updateAriaAlert(messageText) {
|
|
281
|
+
if (messageText) {
|
|
282
|
+
while (this.ariaLiveContainer.firstChild) {
|
|
283
|
+
this.ariaLiveContainer.removeChild(this.ariaLiveContainer.firstChild);
|
|
284
|
+
}
|
|
285
|
+
/* Create new message and append it to the live region */
|
|
286
|
+
const messageNode = document.createTextNode(messageText);
|
|
287
|
+
this.ariaLiveContainer.appendChild(messageNode);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function initialiseProgressButtons(document = window.document) {
|
|
293
|
+
// Create a single live region for button status announcements
|
|
294
|
+
const statusRegion = document.createElement('div');
|
|
295
|
+
statusRegion.setAttribute('aria-live', 'assertive');
|
|
296
|
+
statusRegion.setAttribute('role', 'status');
|
|
297
|
+
statusRegion.className = 'govuk-visually-hidden';
|
|
298
|
+
document.body.appendChild(statusRegion);
|
|
299
|
+
const progressButtons = Array.prototype.slice.call(document.querySelectorAll('[data-frontendui="di-progress-button"]'));
|
|
300
|
+
function findClosestForm(element) {
|
|
301
|
+
let el = element;
|
|
302
|
+
while (el && el.nodeName !== 'FORM') {
|
|
303
|
+
el = el.parentElement;
|
|
304
|
+
}
|
|
305
|
+
return el;
|
|
306
|
+
}
|
|
307
|
+
progressButtons.forEach(function (button) {
|
|
308
|
+
const form = findClosestForm(button);
|
|
309
|
+
let isSubmitting = false;
|
|
310
|
+
// Handle spacebar press for anchor tags
|
|
311
|
+
if (button.tagName.toLowerCase() === 'a') {
|
|
312
|
+
button.addEventListener('keydown', function (event) {
|
|
313
|
+
if (event.code === 'Space') {
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
button.click();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
button.addEventListener('click', function (event) {
|
|
320
|
+
if (isSubmitting) {
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const waitingText = button.getAttribute('data-waiting-text');
|
|
325
|
+
const longWaitingText = button.getAttribute('data-long-waiting-text');
|
|
326
|
+
const errorPage = button.getAttribute('data-error-page');
|
|
327
|
+
const isInput = button.tagName.toLowerCase() === 'input';
|
|
328
|
+
if (!waitingText || !longWaitingText || !errorPage) {
|
|
329
|
+
console.error('Progress button is missing required data attributes.');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
isSubmitting = true;
|
|
333
|
+
// Always handle the button click, regardless of form presence
|
|
334
|
+
handleProgressButtonClick(button, waitingText, longWaitingText, errorPage, isInput);
|
|
335
|
+
// If no form, we're done. If there is a form, the form submit handler will take over
|
|
336
|
+
if (!form) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
// For form buttons, let the click propagate to trigger form submission
|
|
340
|
+
});
|
|
341
|
+
if (form) {
|
|
342
|
+
form.addEventListener('submit', function (event) {
|
|
343
|
+
// The button click handler has already set isSubmitting and handled the button state
|
|
344
|
+
if (isSubmitting) {
|
|
345
|
+
// Allow the first submission, prevent subsequent ones
|
|
346
|
+
if (event.target === form && event.submitter === button) {
|
|
347
|
+
return; // Allow the first submission to proceed
|
|
348
|
+
}
|
|
349
|
+
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// If the form is submitted through another method (not our button),
|
|
353
|
+
// prevent double submission but don't show progress state
|
|
354
|
+
isSubmitting = true;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function handleProgressButtonClick(element, waitingText, longWaitingText, errorPage, isInput) {
|
|
360
|
+
var originalText = isInput && element instanceof HTMLInputElement ? element.value : element.innerText;
|
|
361
|
+
const statusRegion = document.querySelector('.govuk-visually-hidden[role="status"]');
|
|
362
|
+
if (typeof element.blur === 'function') {
|
|
363
|
+
element.blur();
|
|
364
|
+
}
|
|
365
|
+
element.setAttribute('data-prevent-double-click', 'true');
|
|
366
|
+
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
|
|
367
|
+
// When the button is disabled immediately it prevents the initial form submission.
|
|
368
|
+
// TODO: We should revisit this. It would be better to trigger the button actions from the form submission rather than the button click when a form is present.
|
|
369
|
+
setTimeout(() => {
|
|
370
|
+
element.disabled = true;
|
|
371
|
+
}, 0);
|
|
372
|
+
}
|
|
373
|
+
else if (element.tagName.toLowerCase() === 'a') {
|
|
374
|
+
element.setAttribute('aria-disabled', 'true');
|
|
375
|
+
element.style.pointerEvents = 'none';
|
|
376
|
+
}
|
|
377
|
+
element.classList.add('govuk-progress-button--disabled');
|
|
378
|
+
var classes = element.className.split(' ');
|
|
379
|
+
if (classes.indexOf('govuk-button--progress-loading') === -1) {
|
|
380
|
+
classes.push('govuk-button--progress-loading');
|
|
381
|
+
element.className = classes.join(' ');
|
|
382
|
+
}
|
|
383
|
+
if (isInput && element instanceof HTMLInputElement) {
|
|
384
|
+
element.value = waitingText;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
element.innerText = waitingText;
|
|
388
|
+
}
|
|
389
|
+
// Announce the initial waiting state
|
|
390
|
+
if (statusRegion) {
|
|
391
|
+
statusRegion.textContent = waitingText;
|
|
392
|
+
}
|
|
393
|
+
var longWaitTimeout = window.setTimeout(function () {
|
|
394
|
+
if (isInput && element instanceof HTMLInputElement) {
|
|
395
|
+
element.value = longWaitingText;
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
element.innerText = longWaitingText;
|
|
399
|
+
}
|
|
400
|
+
// Announce the long wait state
|
|
401
|
+
if (statusRegion) {
|
|
402
|
+
statusRegion.textContent = longWaitingText;
|
|
403
|
+
}
|
|
404
|
+
}, 5000);
|
|
405
|
+
var errorTimeout = window.setTimeout(function () {
|
|
406
|
+
window.location.href = errorPage;
|
|
407
|
+
}, 10000);
|
|
408
|
+
function resetButton() {
|
|
409
|
+
var classes = element.className.split(' ');
|
|
410
|
+
var loadingIndex = classes.indexOf('govuk-button--progress-loading');
|
|
411
|
+
if (loadingIndex !== -1) {
|
|
412
|
+
classes.splice(loadingIndex, 1);
|
|
413
|
+
element.className = classes.join(' ');
|
|
414
|
+
}
|
|
415
|
+
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
|
|
416
|
+
element.disabled = false;
|
|
417
|
+
}
|
|
418
|
+
else if (element.tagName.toLowerCase() === 'a') {
|
|
419
|
+
element.removeAttribute('aria-disabled');
|
|
420
|
+
element.style.pointerEvents = '';
|
|
421
|
+
}
|
|
422
|
+
element.classList.remove('govuk-progress-button--disabled');
|
|
423
|
+
element.setAttribute('data-prevent-double-click', 'false');
|
|
424
|
+
if (isInput && element instanceof HTMLInputElement) {
|
|
425
|
+
element.value = originalText;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
element.innerText = originalText;
|
|
429
|
+
}
|
|
430
|
+
// Clear status region without announcement
|
|
431
|
+
const statusRegion = document.querySelector('.govuk-visually-hidden[role="status"]');
|
|
432
|
+
if (statusRegion) {
|
|
433
|
+
statusRegion.textContent = '';
|
|
434
|
+
}
|
|
435
|
+
window.clearTimeout(errorTimeout);
|
|
436
|
+
window.clearTimeout(longWaitTimeout);
|
|
437
|
+
}
|
|
438
|
+
element.resetProgressButton = resetButton;
|
|
439
|
+
return resetButton;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
exports.initialiseProgressButtons = initialiseProgressButtons;
|
|
443
|
+
exports.useSpinner = useSpinner;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../frontend-src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-button.d.ts","sourceRoot":"","sources":["../../../../frontend-src/progress-button/progress-button.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,QAAQ,GAAE,QAA0B,QAiF7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.test.d.ts","sourceRoot":"","sources":["../../../../../frontend-src/spinner/__tests__/spinner.test.ts"],"names":[],"mappings":"AAGA,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,oBAE9B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare function useSpinner(containerId: string, pollingFunction: PollingFunction, successFunction: VoidFunction, errorFunction: VoidFunction): Promise<void>;
|
|
2
|
+
export type PollingFunction = (abortSignal: AbortSignal) => Promise<PollResult>;
|
|
3
|
+
export declare enum PollResult {
|
|
4
|
+
Success = 0,
|
|
5
|
+
Failure = 1,
|
|
6
|
+
Pending = 2,
|
|
7
|
+
Backoff = 3
|
|
8
|
+
}
|
|
9
|
+
declare enum SpinnerState {
|
|
10
|
+
Waiting = 0,
|
|
11
|
+
LongWaiting = 1,
|
|
12
|
+
Error = 2,
|
|
13
|
+
Complete = 3
|
|
14
|
+
}
|
|
15
|
+
type SpinnerConfig = {
|
|
16
|
+
msBeforeInformingOfLongWait: number;
|
|
17
|
+
msBeforeAbort: number;
|
|
18
|
+
msBetweenRequests: number;
|
|
19
|
+
msBetweenDomUpdate: number;
|
|
20
|
+
ariaAlertCompletionText?: string;
|
|
21
|
+
hideSpinnerOnError: boolean;
|
|
22
|
+
maxBackoffTries: number;
|
|
23
|
+
};
|
|
24
|
+
export declare class Spinner {
|
|
25
|
+
container: HTMLDivElement;
|
|
26
|
+
noJsContent: HTMLElement;
|
|
27
|
+
waitContent?: HTMLElement;
|
|
28
|
+
longWaitContent?: HTMLElement;
|
|
29
|
+
successContent?: HTMLElement;
|
|
30
|
+
errorContent?: HTMLElement;
|
|
31
|
+
ariaLiveContainer: HTMLDivElement;
|
|
32
|
+
visibleElementsContainer: HTMLDivElement;
|
|
33
|
+
state: SpinnerState;
|
|
34
|
+
displayState?: SpinnerState;
|
|
35
|
+
updateDomTimer?: NodeJS.Timeout;
|
|
36
|
+
abortController: AbortController;
|
|
37
|
+
config: SpinnerConfig;
|
|
38
|
+
backOffCount: number;
|
|
39
|
+
pollingFunction: PollingFunction;
|
|
40
|
+
onSuccess: VoidFunction;
|
|
41
|
+
onError: VoidFunction;
|
|
42
|
+
constructor(domContainer: HTMLDivElement, pollingFunction: PollingFunction, onSuccess: VoidFunction, onError: VoidFunction);
|
|
43
|
+
init(): Promise<void>;
|
|
44
|
+
private getElementOrThrow;
|
|
45
|
+
private getConfig;
|
|
46
|
+
private createAbortController;
|
|
47
|
+
private handleAbort;
|
|
48
|
+
private getInitTime;
|
|
49
|
+
private initTimer;
|
|
50
|
+
private hasCompleted;
|
|
51
|
+
private reflectSuccess;
|
|
52
|
+
private reflectError;
|
|
53
|
+
private reflectLongWait;
|
|
54
|
+
private updateStateAccordingToTimeElapsed;
|
|
55
|
+
private createSpinnerElement;
|
|
56
|
+
private updateDom;
|
|
57
|
+
private cloneAndAddIfExists;
|
|
58
|
+
private callPollingFunction;
|
|
59
|
+
private calculateBackoffTime;
|
|
60
|
+
private createAriaLiveContainer;
|
|
61
|
+
private updateAriaAlert;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=spinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../../frontend-src/spinner/spinner.ts"],"names":[],"mappings":"AAAA,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,iBAoBjJ;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAEhF,oBAAY,UAAU;IACpB,OAAO,IAAA;IACP,OAAO,IAAA;IACP,OAAO,IAAA;IACP,OAAO,IAAA;CACR;AAED,aAAK,YAAY;IACf,OAAO,IAAA;IACP,WAAW,IAAA;IACX,KAAK,IAAA;IACL,QAAQ,IAAA;CACT;AAED,KAAK,aAAa,GAAG;IACnB,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,qBAAa,OAAO;IAClB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,WAAW,CAAC;IAC9B,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,iBAAiB,EAAE,cAAc,CAAC;IAElC,wBAAwB,EAAE,cAAc,CAAC;IAEzC,KAAK,EAAE,YAAY,CAAwB;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAChC,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE,aAAa,CAAC;IACtB,YAAY,EAAE,MAAM,CAAK;IAEzB,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,EAAE,YAAY,CAAC;IACxB,OAAO,EAAE,YAAY,CAAC;gBAGpB,YAAY,EAAE,cAAc,EAC5B,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,YAAY;IA2CjB,IAAI;IAUV,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,SAAS;IA0BjB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,WAAW,CAEjB;IAEF,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,SAAS,CAKf;IAEF,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,cAAc,CAGpB;IAEF,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,iCAAiC,CAWvC;IAEF,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,SAAS,CAkDf;IAEF,OAAO,CAAC,mBAAmB;YASb,mBAAmB;IA2CjC,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,eAAe;CAWxB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export interface virtualDom {
|
|
2
|
+
nodeName?: string;
|
|
3
|
+
id?: string;
|
|
4
|
+
classes?: string[];
|
|
5
|
+
text?: string;
|
|
6
|
+
}
|
|
7
|
+
export type error = {
|
|
8
|
+
spinnerState: string;
|
|
9
|
+
done: boolean;
|
|
10
|
+
virtualDom: never[];
|
|
11
|
+
state: {
|
|
12
|
+
error: boolean;
|
|
13
|
+
};
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
export type apiRoute = RequestInfo | URL;
|
|
17
|
+
export type content = {
|
|
18
|
+
longWait?: {
|
|
19
|
+
spinnerStateText: string;
|
|
20
|
+
};
|
|
21
|
+
initial?: {
|
|
22
|
+
spinnerState: string | virtualDom;
|
|
23
|
+
};
|
|
24
|
+
complete?: {
|
|
25
|
+
spinnerState: string;
|
|
26
|
+
ariaButtonEnabledMessage?: string;
|
|
27
|
+
};
|
|
28
|
+
continueButton?: {
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
error?: {
|
|
32
|
+
heading?: string;
|
|
33
|
+
messageText?: string;
|
|
34
|
+
whatYouCanDo: {
|
|
35
|
+
heading: string;
|
|
36
|
+
message: {
|
|
37
|
+
text1: string;
|
|
38
|
+
link: {
|
|
39
|
+
href: string;
|
|
40
|
+
text: string;
|
|
41
|
+
};
|
|
42
|
+
text2: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export type timers = {
|
|
48
|
+
updateDomTimer?: unknown | number;
|
|
49
|
+
abortUnresponsiveRequest?: unknown | number;
|
|
50
|
+
abort?: unknown | never;
|
|
51
|
+
};
|
|
52
|
+
export type state = {
|
|
53
|
+
abortController: null | {
|
|
54
|
+
abort: any;
|
|
55
|
+
};
|
|
56
|
+
abort?: unknown;
|
|
57
|
+
error?: boolean | error;
|
|
58
|
+
spinnerState?: string;
|
|
59
|
+
done?: boolean;
|
|
60
|
+
virtualDom?: unknown[];
|
|
61
|
+
timers?: {
|
|
62
|
+
timers: timers;
|
|
63
|
+
};
|
|
64
|
+
spinnerStateText?: string;
|
|
65
|
+
buttonDisabled?: boolean;
|
|
66
|
+
heading?: object | string | undefined;
|
|
67
|
+
messageText?: string;
|
|
68
|
+
ariaButtonEnabledMessage?: string;
|
|
69
|
+
};
|
|
70
|
+
export type node = {
|
|
71
|
+
text: object;
|
|
72
|
+
innerHTML: HTMLElement;
|
|
73
|
+
id: string;
|
|
74
|
+
classes: string[];
|
|
75
|
+
nodeName: keyof HTMLElementTagNameMap;
|
|
76
|
+
textContent: unknown | HTMLElement | string;
|
|
77
|
+
el: unknown | HTMLElement | string;
|
|
78
|
+
buttonDisabled: boolean | string;
|
|
79
|
+
};
|
|
80
|
+
export type initialState = {
|
|
81
|
+
nodeName?: string;
|
|
82
|
+
id?: string;
|
|
83
|
+
classes?: (string | undefined)[];
|
|
84
|
+
text?: object | string;
|
|
85
|
+
buttonDisabled?: state;
|
|
86
|
+
}[];
|
|
87
|
+
export type spinnerInitial = {
|
|
88
|
+
container?: HTMLElement;
|
|
89
|
+
apiUrl?: string;
|
|
90
|
+
msBeforeInformingOfLongWait?: number;
|
|
91
|
+
msBeforeAbort?: number;
|
|
92
|
+
msBetweenRequests?: number;
|
|
93
|
+
msBeforeDomUpdate?: number;
|
|
94
|
+
ariaLiveContainer?: HTMLElement;
|
|
95
|
+
domRequirementsMet?: boolean;
|
|
96
|
+
initTime?: number | undefined;
|
|
97
|
+
updateDomTimer?: number | undefined | any;
|
|
98
|
+
abortController?: any | object;
|
|
99
|
+
config?: {
|
|
100
|
+
msBeforeAbort: number;
|
|
101
|
+
msBeforeInformingOfLongWait: number;
|
|
102
|
+
msBetweenRequests: number;
|
|
103
|
+
ariaButtonEnabledMessage: string;
|
|
104
|
+
msBetweenDomUpdate: number;
|
|
105
|
+
};
|
|
106
|
+
msBetweenDomUpdate?: number;
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../frontend-src/utils/types.ts"],"names":[],"mappings":"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"}
|