@govuk-one-login/frontend-ui 2.0.0 → 3.1.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 (38) hide show
  1. package/README.md +37 -0
  2. package/build/all.css +1 -1
  3. package/build/cjs/backend/index.cjs +16 -2
  4. package/build/cjs/backend/index.d.cts +12 -0
  5. package/build/cjs/backend/index.d.ts +12 -0
  6. package/build/cjs/backend/index.d.ts.map +1 -1
  7. package/build/cjs/frontend/index.cjs +288 -268
  8. package/build/cjs/frontend/index.d.cts +3 -1
  9. package/build/cjs/frontend/index.d.ts +3 -1
  10. package/build/cjs/frontend/index.d.ts.map +1 -1
  11. package/build/cjs/frontend/progress-button/progress-button.d.ts +2 -0
  12. package/build/cjs/frontend/progress-button/progress-button.d.ts.map +1 -0
  13. package/build/cjs/frontend/spinner/__tests__/spinner.test.d.ts +0 -4
  14. package/build/cjs/frontend/spinner/__tests__/spinner.test.d.ts.map +1 -1
  15. package/build/cjs/frontend/spinner/spinner.d.ts +58 -84
  16. package/build/cjs/frontend/spinner/spinner.d.ts.map +1 -1
  17. package/build/components/_all.scss +1 -0
  18. package/build/components/progress-button/_index.scss +44 -0
  19. package/build/components/progress-button/macro.njk +2 -0
  20. package/build/components/progress-button/progress-button.yaml +13 -0
  21. package/build/components/progress-button/template.njk +115 -0
  22. package/build/components/spinner/README.md +75 -52
  23. package/build/components/spinner/_index.scss +2 -11
  24. package/build/components/spinner/template.njk +15 -8
  25. package/build/esm/backend/index.d.ts +12 -0
  26. package/build/esm/backend/index.d.ts.map +1 -1
  27. package/build/esm/backend/index.js +16 -2
  28. package/build/esm/frontend/index.d.ts +3 -1
  29. package/build/esm/frontend/index.d.ts.map +1 -1
  30. package/build/esm/frontend/index.js +288 -269
  31. package/build/esm/frontend/progress-button/progress-button.d.ts +2 -0
  32. package/build/esm/frontend/progress-button/progress-button.d.ts.map +1 -0
  33. package/build/esm/frontend/spinner/__tests__/spinner.test.d.ts +0 -4
  34. package/build/esm/frontend/spinner/__tests__/spinner.test.d.ts.map +1 -1
  35. package/build/esm/frontend/spinner/spinner.d.ts +58 -84
  36. package/build/esm/frontend/spinner/spinner.d.ts.map +1 -1
  37. package/package.json +3 -2
  38. package/build/components/spinner/api.njk +0 -27
@@ -1,90 +1,64 @@
1
- export declare function useSpinner(): void;
2
- type TVDOM = {
3
- classes: string[];
4
- buttonDisabled?: boolean;
5
- id?: string;
6
- innerHTML?: string;
7
- nodeName: string;
8
- text?: string;
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;
9
23
  };
10
24
  export declare class Spinner {
11
- container: HTMLElement;
12
- spinnerContainer: HTMLElement;
13
- ariaLiveContainer: HTMLElement;
14
- content: {
15
- complete: {
16
- ariaButtonEnabledMessage?: string;
17
- spinnerState: string;
18
- };
19
- continueButton: {
20
- text: string;
21
- };
22
- error: {
23
- heading: string;
24
- messageText: string;
25
- whatYouCanDo: {
26
- heading: string;
27
- message: {
28
- text1: string;
29
- link: {
30
- href: string;
31
- text: string;
32
- };
33
- text2: string;
34
- };
35
- };
36
- };
37
- initial: {
38
- heading: string;
39
- spinnerStateText: string;
40
- spinnerState: string;
41
- };
42
- longWait: {
43
- spinnerStateText: string;
44
- };
45
- };
46
- domRequirementsMet?: boolean;
47
- state: {
48
- ariaButtonEnabledMessage?: string;
49
- buttonDisabled: boolean;
50
- done: boolean;
51
- error: boolean;
52
- heading: string;
53
- messageText?: string;
54
- spinnerState: string;
55
- spinnerStateText: string;
56
- virtualDom: TVDOM[];
57
- };
58
- updateDomTimer: NodeJS.Timeout;
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;
59
36
  abortController: AbortController;
60
- config: {
61
- apiUrl: string;
62
- ariaButtonEnabledMessage?: string;
63
- msBeforeInformingOfLongWait: number;
64
- msBeforeAbort: number;
65
- msBetweenRequests: number;
66
- msBetweenDomUpdate: number;
67
- };
68
- notInErrorOrDoneState: () => boolean;
69
- reflectCompletion: () => void;
70
- reflectError: () => void;
71
- reflectLongWait(): void;
72
- updateAccordingToTimeElapsed: (initTime: number) => void;
73
- initialiseState(): void;
74
- initialiseContent(element: HTMLElement): void;
75
- createVirtualDom(): TVDOM[];
76
- vDomHasChanged: (currentVDom: TVDOM[], nextVDom: TVDOM[]) => boolean;
77
- convert: (node: TVDOM) => HTMLElement;
78
- updateAriaAlert: (messageText: string) => void;
79
- updateDom: () => void;
80
- requestIDProcessingStatus(initTime: number): Promise<void>;
81
- initialiseContainers: () => void;
82
- getInitTime(): number;
83
- initTimer: (initTime: number) => void;
84
- handleAbort: () => void;
85
- initialiseAbortController: () => void;
86
- init(): void;
87
- constructor(domContainer: HTMLElement);
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;
88
62
  }
89
63
  export {};
90
64
  //# sourceMappingURL=spinner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.d.ts","sourceRoot":"","sources":["../../../../frontend-src/spinner/spinner.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,SAWzB;AAED,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,OAAO;IAClB,SAAS,cAAC;IACV,gBAAgB,EAAE,WAAW,CAAC;IAC9B,iBAAiB,EAAE,WAAW,CAAC;IAC/B,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,wBAAwB,CAAC,EAAE,MAAM,CAAC;YAClC,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,cAAc,EAAE;YACd,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;QACF,KAAK,EAAE;YACL,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC;YACpB,YAAY,EAAE;gBACZ,OAAO,EAAE,MAAM,CAAC;gBAChB,OAAO,EAAE;oBACP,KAAK,EAAE,MAAM,CAAC;oBACd,IAAI,EAAE;wBACJ,IAAI,EAAE,MAAM,CAAC;wBACb,IAAI,EAAE,MAAM,CAAC;qBACd,CAAC;oBACF,KAAK,EAAE,MAAM,CAAC;iBACf,CAAC;aACH,CAAC;SACH,CAAC;QACF,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC;YAChB,gBAAgB,EAAE,MAAM,CAAC;YACzB,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,QAAQ,EAAE;YACR,gBAAgB,EAAE,MAAM,CAAC;SAC1B,CAAC;KACH,CAAC;IACF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE;QACL,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,cAAc,EAAE,OAAO,CAAC;QACxB,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EAAE,OAAO,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,KAAK,EAAE,CAAC;KACrB,CAAC;IACF,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,2BAA2B,EAAE,MAAM,CAAC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAMC;IAEF,qBAAqB,gBAEnB;IAEF,iBAAiB,aAQf;IAEF,YAAY,aAQV;IAEF,eAAe;IAMf,4BAA4B,GAAI,UAAU,MAAM,UAO9C;IAEF,eAAe;IAef,iBAAiB,CAAC,OAAO,EAAE,WAAW;IA8EtC,gBAAgB,IAAI,KAAK,EAAE;IAwD3B,cAAc,GAAI,aAAa,KAAK,EAAE,EAAE,UAAU,KAAK,EAAE,aAEvD;IAEF,OAAO,GAAI,MAAM,KAAK,iBASpB;IAEF,eAAe,GAAI,aAAa,MAAM,UAQpC;IAEF,SAAS,aA0BP;IAEI,yBAAyB,CAAC,QAAQ,EAAE,MAAM;IAgChD,oBAAoB,aAUlB;IAEF,WAAW;IAYX,SAAS,GAAI,UAAU,MAAM,UAO3B;IAEF,WAAW,aAET;IAEF,yBAAyB,aAIvB;IAEF,IAAI;gBAWQ,YAAY,EAAE,WAAW;CAMtC"}
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"}
@@ -3,3 +3,4 @@
3
3
  @use "./language-select";
4
4
  @use "./spinner";
5
5
  @use "./footer";
6
+ @use "./progress-button";
@@ -0,0 +1,44 @@
1
+ .govuk-button--progress{
2
+ border-bottom: 8px;
3
+ position: relative;
4
+ }
5
+
6
+ .govuk-button--progress-loading{
7
+ background-color: #505A5F !important;
8
+ color: #fff !important;
9
+ pointer-events: none !important;
10
+ padding-left: 40px !important;
11
+ }
12
+
13
+ @keyframes rotate {
14
+ from {
15
+ transform: rotate(0);
16
+ }
17
+
18
+ to {
19
+ transform: rotate(360deg);
20
+ }
21
+ }
22
+
23
+
24
+ .govuk-button--progress-loading::before {
25
+ content: "";
26
+ border: 3px solid rgba(255, 255, 255, 0.35);
27
+ border-top: 3px solid #fff;
28
+ border-radius: 50%;
29
+ width: 20px;
30
+ height: 20px;
31
+ animation: rotate 1s infinite linear;
32
+ position: absolute;
33
+ top: 12% !important;
34
+ left: 8px;
35
+ }
36
+
37
+ @media (prefers-reduced-motion: reduce) {
38
+ .govuk-button--progress-loading {
39
+ animation: none;
40
+ }
41
+ }
42
+
43
+
44
+
@@ -0,0 +1,2 @@
1
+ {% macro frontendUiProgressButton(params) %} {%- include "./template.njk" -%} {% endmacro
2
+ %}
@@ -0,0 +1,13 @@
1
+ params:
2
+ - name: translations
3
+ type: string
4
+ required: true
5
+ description: translations from middleware
6
+ - name: href
7
+ type: string
8
+ required: true
9
+ description: URL for the button to link to
10
+ - name: errorPage
11
+ type: string
12
+ required: true
13
+ description: URL for the error help page
@@ -0,0 +1,115 @@
1
+ {% set progressButton = params.translations %}
2
+ <noscript>
3
+ <style>
4
+ .noscript {
5
+ border: #F3F3F3 3px solid;
6
+ padding-top: 28px;
7
+ padding-left: 12px;
8
+ padding-right: 12px;
9
+ margin-bottom: 12px;
10
+ }
11
+ </style>
12
+ </noscript>
13
+
14
+ {# Set classes for this component #}
15
+ {%- set classNames = "govuk-button govuk-button--progress" -%}
16
+
17
+ {%- if params.classes %}
18
+ {% set classNames = classNames + " " + params.classes %}
19
+ {% endif %}
20
+ {% if params.disabled %}
21
+ {% set classNames = classNames + " govuk-button--disabled" %}
22
+ {% endif -%}
23
+
24
+ {%- if params.element %}
25
+ {% set element = params.element | lower %}
26
+ {% else %}
27
+ {% if params.href %}
28
+ {% set element = 'a' %}
29
+ {% else %}
30
+ {% set element = 'button' %}
31
+ {% endif %}
32
+ {% endif -%}
33
+
34
+ {% if params.isStartButton %}
35
+ {% set iconHtml %}
36
+ {#- The SVG needs `focusable="false"` so that Internet Explorer does not
37
+ treat it as an interactive element - without this it will be
38
+ 'focusable' when using the keyboard to navigate. #}
39
+ <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false">
40
+ <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"/>
41
+ </svg>
42
+ {% endset %}
43
+ {% set classNames = classNames + " govuk-button--start" %}
44
+ {% endif %}
45
+
46
+ {#- Define common attributes that we can use across all element types #}
47
+
48
+ {%- set commonAttributes %} data-module="govuk-button"{% for attribute, value in params.attributes %}
49
+ {{attribute}}="{{value}}"{% endfor %}
50
+ {% if params.id %} id="{{ params.id }}"{% endif %}
51
+ {% endset %}
52
+
53
+ {#- Define common attributes we can use for both button and input types #}
54
+
55
+ {%- set buttonAttributes %}
56
+ {% if params.name %} name="{{ params.name }}"{% endif %}
57
+ {% if params.disabled %} disabled="disabled" aria-disabled="true"{% endif %}
58
+ {% if params.preventDoubleClick !== undefined %} data-prevent-double-click="{{params.preventDoubleClick}}"{% endif %}{% endset %}
59
+
60
+ {%- if element == 'a' %}
61
+ <div class="noscript">
62
+ <noscript>
63
+ <div class="govuk-body" >
64
+ {{ progressButton.noJavascriptMessage }}
65
+ </div>
66
+ </noscript>
67
+
68
+ <a href="{{ params.href if params.href else '#' }}" role="button" draggable="false" {{- commonAttributes | safe }} class = "{{ classNames }}"
69
+ aria-live="assertive" data-frontendui="di-progress-button" data-waiting-text="{{ progressButton.waitingText }}" data-long-waiting-text="{{ progressButton.longWaitingText }}" data-error-page="{{ params.errorPage | default('#') }}" >
70
+ {% if params.html %}
71
+ {{ params.html | safe }}
72
+ {% elseif params.text %}
73
+ {{ params.text | safe }}
74
+ {% else %}
75
+ {{ progressButton.text }}
76
+ {% endif %}
77
+ {{- iconHtml | safe | trim | indent(2, true) if iconHtml -}}
78
+ </a>
79
+ </div>
80
+
81
+ {%- elseif element == 'button' %}
82
+ <div class="noscript">
83
+ <noscript>
84
+ <div class="govuk-body" >
85
+ {{ progressButton.noJavascriptMessage }}
86
+ </div>
87
+ </noscript>
88
+
89
+ <button {%- if params.value %} value="{{ params.value }}"{% endif %}{%- if params.type %} type="{{ params.type }}"{% endif %} {{- buttonAttributes | safe }} {{- commonAttributes | safe }} class = "{{ classNames }}"
90
+ aria-live="assertive" data-frontendui="di-progress-button" data-waiting-text="{{ progressButton.waitingText }}" data-long-waiting-text="{{ progressButton.longWaitingText }}" data-error-page="{{ params.errorPage | default('#') }}" >
91
+ {% if params.html %}
92
+ {{ params.html | safe }}
93
+ {% elseif params.text %}
94
+ {{ params.text | safe }}
95
+ {% else %}
96
+ {{ progressButton.text }}
97
+ {% endif %}
98
+ {{- iconHtml | safe | trim | indent(2, true) if iconHtml -}}
99
+
100
+ </button>
101
+ </div>
102
+
103
+ {%- elseif element == 'input' %}
104
+ <div class="noscript">
105
+ <noscript>
106
+ <div class="govuk-body" >
107
+ {{ progressButton.noJavascriptMessage }}
108
+ </div>
109
+ </noscript>
110
+ <input value="{{ params.value }}" type="{{ params.type if params.type else 'submit'}}"{{- buttonAttributes | safe }} {{- commonAttributes | safe }} class = "{{ classNames }}"
111
+ aria-live="assertive" data-frontendui="di-progress-button" data-waiting-text="{{ progressButton.waitingText }}" data-long-waiting-text="{{ progressButton.longWaitingText }}" data-error-page="{{ params.errorPage | default('#') }}" >
112
+ {{- iconHtml | safe | trim | indent(2, true) if iconHtml -}}
113
+ </div>
114
+
115
+ {% endif %}
@@ -1,47 +1,87 @@
1
1
  # Spinner
2
2
 
3
- This component can be imported to be displayed before the page loads. Currently the logic has been set up to **display** the spinner.
4
-
5
- Should you need to ensure it displays before the page renders any data, this should be done within the app.
6
-
7
- ## Timer
3
+ This component:
4
+ - Displays an animated spinner graphic
5
+ - Repeatedly calls a supplied function
6
+ - Stops the spinner animation when the function returns success, failure, or doesn't return either after a certain amount of time
7
+ - Displays different HTML for the different phases of its lifecycle
8
+
9
+ It consists of code in `../../frontend-src/spinner/spinner.ts` and styling in `_index.scss`
10
+
11
+ ## Requirements
12
+
13
+ For the spinner to perform correctly it requires the following:
14
+ - A container `div` to display within
15
+ - A polling function to call repeatedly until it returns success or failure
16
+ - An optional success function to call once if the polling function returns success
17
+ - An optional error function to call once if the polling function returns failure, or we reach the abort duration
18
+ - A div with id `no-js-content`. This should contain the HTML to display if the user has JS disabled
19
+ - An optional div with id `wait-content`. This should contain the default HTML to display under the spinner
20
+ - An optional div with id `long-wait-content`. This should contain the HTML to display under the spinner once it has been spinning for the specified long wait duration
21
+ - An optional div with id `success-content`. This should contain the HTML to display under the spinner once the polling function returns success
22
+ - An optional div with id `error-content`. This should contain the HTML to display under the spinner if the polling function returns failure, or doesn't return success before the abort duration is reached
23
+ - An optional data attribute `data-ms-before-informing-of-long-wait` to set the amount of time the spinner will spin before displaying the `long-wait-content`
24
+ - An optional data attribute `data-ms-before-abort` to set the amount of time the spinner will spin before displaying the `error-content` and calling the `errorFunction`
25
+ - An optional data attribute `data-ms-between-dom-update` to set the amount of time between updates to the spinner UI
26
+ - An optional data attribute `data-ms-between-requests` to set the amount of time between calls to the `pollingFunction`
27
+ - An optional data attribute `data-hide-spinner-on-error` to hide the spinner graphic on error (defaults to false)
28
+ - An optional data attribute `data-max-backoff-tries` to set the number of times in a row the polling function can return `backoff` before the error content is displayed (defaults to 3)
29
+ - An optional data attribute `aria-alert-completion-text`. If supplied this text will be set as an aria alert if the spinner completes successfully
30
+
31
+ For example:
32
+
33
+ ```html
34
+ <div id="spinner-container"
35
+ data-ms-before-informing-of-long-wait="10000"
36
+ data-ms-before-abort="30000"
37
+ data-ms-between-dom-update="1000"
38
+ data-ms-between-requests="2000"
39
+ data-hide-spinner-on-error="true"
40
+ data-max-backoff-tries="3"
41
+ data-aria-alert-completion-text="Task completed successfully, you may now continue">
42
+ <div id="no-js-content"><p class="centre govuk-body">JS is disabled</p></div>
43
+ <div id="wait-content" style="display:none"><p class="centre govuk-body">Waiting</p></div>
44
+ <div id="long-wait-content" style="display:none"><p class="centre govuk-body">Still waiting</p></div>
45
+ <div id="success-content" style="display:none"><p class="centre govuk-body">Success!</p></div>
46
+ <div id="error-content" style="display:none"><p class="centre govuk-body">Error :(</p></div>
47
+ </div>
48
+ ```
8
49
 
9
- The timer is set to two seconds and this is done in the route. There is a file called `api.njk` and this binds the data attributes that are needed for the script to be run.
50
+ Note that the content divs (except for the no-js div) have `style="display:none"` to hide them until they are needed.
10
51
 
11
- ```njk
12
- {% extends "layouts/main.njk" %}
52
+ ```typescript
53
+ async function pollFunction(abortSignal): PollResult {
54
+ // Check for success
55
+ }
13
56
 
14
- {% set url = "/api" %}
57
+ function successFunction(): void {
58
+ // Optionally do stuff, e.g. enable a button, redirect, etc
59
+ }
15
60
 
16
- {% block content %}
17
- <div id="spinner-container"
18
- data-initial-heading="Initial heading text"
19
- data-initial-spinnerStateText="Initial spinner state text"
20
- data-initial-spinnerState="pending"
21
- data-error-heading="Error heading"
22
- data-error-messageText="Error message text"
23
- data-error-whatYouCanDo-heading="Error what you can do heading"
24
- data-error-whatYouCanDo-message-text1="Error what you can do message text1"
25
- data-error-whatYouCanDo-message-link-href="Error what you can do message link href"
26
- data-error-whatYouCanDo-message-link-text="Error what you can do message link text"
27
- data-error-whatYouCanDo-message-text2="Error what you can do message link text2"
28
- data-complete-spinnerState="complete"
29
- data-longWait-spinnerStateText="Long wait spinner state">
30
- </div>
61
+ function errorFunction(): void {
62
+ // Optionally do stuff, e.g. enable a button, redirect, etc
63
+ }
31
64
 
32
- {% endblock %}
65
+ useSpinner("spinner-container", pollingFunction, successFunction, errorFunction);
66
+ ```
33
67
 
34
- {% block pageScripts %}
68
+ ## Lifecycle
35
69
 
36
- {% endblock %}
70
+ The spinner starts in the waiting state and displays the `wait` content under the animated spinner graphic content.
71
+ If a call to the polling function returns `success` the spinner will stop animating, display the `success-content` content, and call the `successFunction` (if specified)
72
+ If a call to the polling function returns `failure` the spinner will stop animating, display the `error-content` content, and call the `errorFunction` (if specified)
73
+ If the spinner waits past the `long-wait` duration while the polling function keeps returning `pending` the spinner will display the `long-wait-content` content
74
+ If the spinner waits past the `abort` duration while the polling function keeps returning `pending` the spinner will stop animating, display the `error-content` content, and call the `errorFunction` (if specified)
75
+ The polling function can optionally return `backoff` if it should be tried again but after a delay (e.g. to account for network instability). On receiving `backoff` the spinner will exponentially increase the time before the
76
+ next call to the polling function. If the polling function returns `backoff` more times than the `max-backoff-tries` value in a row then the final call is treated as if had returned `failure` instead. If a call to the polling
77
+ function returns `pending` then the backoff counter resets.
37
78
 
79
+ ### Page refreshes
38
80
 
39
- ```
81
+ When the spinner starts for the first time it stores the current time in session storage. If the page is refreshed the spinner will retrieve the initial start time from session storage and update itself appropriately.
40
82
 
41
83
  ## Scripts
42
84
 
43
- The script finds the DOM element of the ID `spinner-container` and then runs the animation, it has the flexibility for an error state to be handled or for spinner to take longer/shorter.
44
-
45
85
  Within the frontend-ui package, this script is bundled within `frontend-src`, so that it can be used in the alpha-app.
46
86
 
47
87
  Please note that in the `package.json` file of the frontend-ui repo the files needed for the spinner are exported as follows:
@@ -63,11 +103,13 @@ The default import is at the top and the one at the bottom ensures, the correct
63
103
 
64
104
  Configurations to the alpha-app have also been made to ensure these script files work accordingly however this should not affect any users wanting to use the spinner component from the frontend-ui package.
65
105
 
66
- ## Routes
106
+ ## Manual Testing
107
+
108
+ The nunjucks files in this folder are to allow automated accessibility testing, most users of the spinner component will only need the CSS and script files.
67
109
 
68
- The way testing is done for visual purposes of the components within the frontend-ui package is through the alpha-app.
110
+ There is a `/spinner` page in the alpha-app that will display the spinner component.
69
111
 
70
- The routes are set with Express and it is important to note, this route setting is important within the user's application to ensure the spinner works effectively.
112
+ An endpoint has been created in the alpha-app at `/api` that will return a pending result for a specified number of requests before returning a success response.
71
113
 
72
114
  ```js
73
115
  let counter = 0;
@@ -86,22 +128,3 @@ app.get("/api", (req, res) => {
86
128
  }
87
129
  });
88
130
  ```
89
-
90
- This block of code is used **before** any middleware is set, to ensure the params at the end of the route can be captured. This is also helpful as usually the spinner is the first thing to render, whilst the page waits for data to be loaded.
91
-
92
- There is a counter set here and within `template.njk` the url sets the processing time (defaulted to 2 currently).
93
-
94
- ```njk
95
- {% block content %}
96
-
97
- {% set url = "/api?processingTime=2" %}
98
-
99
-
100
- <div id="spinner-container" data-api-route="{{ url }}">
101
-
102
- </div>
103
-
104
- {% endblock %}
105
- ```
106
-
107
- It is important that the url is set and the page uses the correct ID.
@@ -6,7 +6,7 @@
6
6
  border-style: solid;
7
7
  border-color: #dee0e2;
8
8
  border-top-color: #005ea5;
9
- margin-bottom: govuk-spacing(3);
9
+ margin-bottom: 15px;
10
10
 
11
11
  @media (forced-colors: active) {
12
12
  forced-color-adjust: none;
@@ -22,22 +22,13 @@
22
22
  transform: rotate(0.125turn);
23
23
  }
24
24
 
25
- &__ready {
25
+ &__finished {
26
26
  border-color: #005ea5;
27
27
  -webkit-animation: none;
28
28
  animation: none;
29
29
  }
30
30
  }
31
31
 
32
- #spinner-container {
33
- &__error {
34
- .spinner,
35
- .spinner-state-text {
36
- display: none;
37
- }
38
- }
39
- }
40
-
41
32
  @-webkit-keyframes spin {
42
33
  0% {
43
34
  -webkit-transform: rotate(0deg);
@@ -1,11 +1,18 @@
1
+ {# This template is for use in accessbility testing. #}
1
2
  {% block content %}
2
3
 
3
- {% set url = "/api?processingTime=2" %}
4
+ <main>
5
+ <div id="spinner-container"
6
+ data-ms-before-informing-of-long-wait="4000"
7
+ data-ms-before-abort="9000"
8
+ data-ms-between-dom-update="1000"
9
+ data-ms-between-requests="3000">
10
+ <div id="no-js-content"><p class="centre govuk-body">JS is disabled</p></div>
11
+ <div id="wait-content" style="display:none"><p class="centre govuk-body">Waiting</p></div>
12
+ <div id="long-wait-content" style="display:none"><p class="centre govuk-body">Still waiting</p></div>
13
+ <div id="success-content" style="display:none"><p class="centre govuk-body">Success!</p></div>
14
+ <div id="error-content" style="display:none"><p class="centre govuk-body">Error :(</p></div>
15
+ </div>
16
+ </main>
4
17
 
5
-
6
- <div id="spinner-container" data-api-route="{{ url }}">
7
-
8
- </div>
9
-
10
-
11
- {% endblock %}
18
+ {% endblock %}
@@ -91,6 +91,12 @@ export declare const frontendUiTranslationEn: {
91
91
  skipLink: {
92
92
  title: string;
93
93
  };
94
+ progressButton: {
95
+ text: string;
96
+ waitingText: string;
97
+ longWaitingText: string;
98
+ noJavascriptMessage: string;
99
+ };
94
100
  };
95
101
  export declare const frontendUiTranslationCy: {
96
102
  cookieBanner: {
@@ -149,6 +155,12 @@ export declare const frontendUiTranslationCy: {
149
155
  skipLink: {
150
156
  title: string;
151
157
  };
158
+ progressButton: {
159
+ text: string;
160
+ waitingText: string;
161
+ longWaitingText: string;
162
+ noJavascriptMessage: string;
163
+ };
152
164
  };
153
165
  export {};
154
166
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQ1D,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAGF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC;AACrD,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQ1D,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAGF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC;AACrD,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAgB,CAAC"}
@@ -71,13 +71,20 @@ var phaseBanner$1 = {
71
71
  var skipLink$1 = {
72
72
  title: "Neidio i'r prif gynnwys"
73
73
  };
74
+ var progressButton$1 = {
75
+ text: "Parhau",
76
+ waitingText: "Aros",
77
+ longWaitingText: "Parhau i aros",
78
+ noJavascriptMessage: "Gall gymryd hyd at 10 eiliad i barhau i'r dudalen nesaf. Ar ôl i chi barhau, peidiwch ag ail-lwytho na chau'r dudalen hon."
79
+ };
74
80
  var translationCy = {
75
81
  cookieBanner: cookieBanner$1,
76
82
  footer: footer$1,
77
83
  header: header$1,
78
84
  languageSelect: languageSelect$1,
79
85
  phaseBanner: phaseBanner$1,
80
- skipLink: skipLink$1
86
+ skipLink: skipLink$1,
87
+ progressButton: progressButton$1
81
88
  };
82
89
 
83
90
  var cookieBanner = {
@@ -150,13 +157,20 @@ var phaseBanner = {
150
157
  var skipLink = {
151
158
  title: "Skip to main content"
152
159
  };
160
+ var progressButton = {
161
+ text: "Continue",
162
+ waitingText: "Wait",
163
+ longWaitingText: "Keep waiting",
164
+ noJavascriptMessage: "It can take up to 10 seconds to continue to the next page. After you continue, do not reload or close this page."
165
+ };
153
166
  var translationEn = {
154
167
  cookieBanner: cookieBanner,
155
168
  footer: footer,
156
169
  header: header,
157
170
  languageSelect: languageSelect,
158
171
  phaseBanner: phaseBanner,
159
- skipLink: skipLink
172
+ skipLink: skipLink,
173
+ progressButton: progressButton
160
174
  };
161
175
 
162
176
  // Implementation