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