@ons/design-system 44.0.0 → 44.1.2
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/components/address-input/_macro.njk +1 -0
- package/components/address-input/autosuggest.address.js +0 -1
- package/components/autosuggest/_macro.njk +1 -0
- package/components/autosuggest/autosuggest.ui.js +1 -1
- package/components/checkboxes/_checkbox.scss +1 -1
- package/components/checkboxes/_macro.njk +3 -3
- package/components/collapsible/_collapsible.scss +1 -1
- package/components/input/_input.scss +6 -2
- package/components/message/_message.scss +3 -1
- package/components/modal/_macro.njk +23 -0
- package/components/modal/_modal.scss +32 -0
- package/components/modal/modal.dom.js +14 -0
- package/components/modal/modal.js +104 -0
- package/components/summary/_summary.scss +4 -2
- package/components/table/_table.scss +1 -1
- package/components/timeout-modal/_macro.njk +26 -0
- package/components/timeout-modal/timeout.dom.js +16 -0
- package/components/timeout-modal/timeout.js +289 -0
- package/css/census.css +1 -1
- package/css/error.css +1 -1
- package/css/main.css +1 -1
- package/favicons/census/cy/android-chrome-192x192.png +0 -0
- package/favicons/census/cy/android-chrome-512x512.png +0 -0
- package/favicons/census/cy/apple-touch-icon.png +0 -0
- package/favicons/census/cy/browserconfig.xml +12 -0
- package/favicons/census/cy/favicon-16x16.png +0 -0
- package/favicons/census/cy/favicon-32x32.png +0 -0
- package/favicons/census/cy/favicon.ico +0 -0
- package/favicons/census/cy/manifest.json +20 -0
- package/favicons/census/cy/mstitle-150x150.png +0 -0
- package/favicons/census/cy/mstitle-310x150.png +0 -0
- package/favicons/census/cy/mstitle-310x310.png +0 -0
- package/favicons/census/cy/mstitle-70x70.png +0 -0
- package/favicons/census/cy/opengraph.png +0 -0
- package/favicons/census/cy/safari-pinned-tab.svg +3 -0
- package/favicons/census/cy/site.webmanifest +19 -0
- package/favicons/census/cy/twitter.png +0 -0
- package/favicons/census/en/android-chrome-192x192.png +0 -0
- package/favicons/census/en/android-chrome-512x512.png +0 -0
- package/favicons/census/en/apple-touch-icon.png +0 -0
- package/favicons/census/en/browserconfig.xml +12 -0
- package/favicons/census/en/favicon-16x16.png +0 -0
- package/favicons/census/en/favicon-32x32.png +0 -0
- package/favicons/census/en/favicon.ico +0 -0
- package/favicons/census/en/manifest.json +20 -0
- package/favicons/census/en/mstitle-150x150.png +0 -0
- package/favicons/census/en/mstitle-310x150.png +0 -0
- package/favicons/census/en/mstitle-310x310.png +0 -0
- package/favicons/census/en/mstitle-70x70.png +0 -0
- package/favicons/census/en/opengraph.png +0 -0
- package/favicons/census/en/safari-pinned-tab.svg +3 -0
- package/favicons/census/en/site.webmanifest +19 -0
- package/favicons/census/en/twitter.png +0 -0
- package/favicons/census/ni/favicon.ico +0 -0
- package/package.json +8 -11
- package/scripts/main.es5.js +2 -2
- package/scripts/main.js +1 -1
- package/scss/base/_global.scss +9 -5
- package/scss/error.scss +0 -2
- package/scss/helpers/_functions.scss +4 -2
- package/scss/main.scss +65 -8
- package/scss/overrides/rtl.scss +4 -2
- package/scss/patternlib.scss +3 -2
- package/scss/utilities/_colors.scss +4 -4
- package/scss/utilities/_grid.scss +7 -5
- package/src/js/domready.js +17 -0
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"isEditable": params.isEditable,
|
|
132
132
|
"ariaYouHaveSelected": params.ariaYouHaveSelected,
|
|
133
133
|
"ariaMinChars": params.ariaMinChars,
|
|
134
|
+
"minChars": params.minChars,
|
|
134
135
|
"ariaResultsLabel": params.ariaResultsLabel,
|
|
135
136
|
"ariaOneResult": params.ariaOneResult,
|
|
136
137
|
"ariaNResults": params.ariaNResults,
|
|
@@ -54,7 +54,6 @@ export default class AutosuggestAddress {
|
|
|
54
54
|
suggestionFunction: this.suggestAddresses.bind(this),
|
|
55
55
|
sanitisedQueryReplaceChars: this.addressReplaceChars,
|
|
56
56
|
sanitisedQuerySplitNumsChars: this.sanitisedQuerySplitNumsChars,
|
|
57
|
-
minChars: 3,
|
|
58
57
|
suggestOnBoot: true,
|
|
59
58
|
handleUpdate: true,
|
|
60
59
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
class="{% if not params.externalInitialiser %}ons-js-autosuggest {% endif %}{% if params.isEditable == false %}ons-js-address-not-editable{% endif %}{% if params.mandatory is defined and params.mandatory == true %} ons-js-address-mandatory{% endif %} {% if params.containerClasses is defined and params.containerClasses %} {{ params.containerClasses }}{% endif %} ons-autosuggest-input"
|
|
7
7
|
data-instructions="{{ params.instructions }}"
|
|
8
8
|
data-aria-you-have-selected="{{ params.ariaYouHaveSelected }}"
|
|
9
|
+
data-min-chars="{{ params.minChars }}"
|
|
9
10
|
data-aria-min-chars="{{ params.ariaMinChars }}"
|
|
10
11
|
data-aria-one-result="{{ params.ariaOneResult }}"
|
|
11
12
|
data-aria-n-results="{{ params.ariaNResults }}"
|
|
@@ -52,6 +52,7 @@ export default class AutosuggestUI {
|
|
|
52
52
|
// Settings
|
|
53
53
|
this.autosuggestData = autosuggestData || context.getAttribute('data-autosuggest-data');
|
|
54
54
|
this.ariaYouHaveSelected = ariaYouHaveSelected || context.getAttribute('data-aria-you-have-selected');
|
|
55
|
+
this.minChars = minChars || context.getAttribute('data-min-chars') || 3;
|
|
55
56
|
this.ariaMinChars = ariaMinChars || context.getAttribute('data-aria-min-chars');
|
|
56
57
|
this.ariaOneResult = ariaOneResult || context.getAttribute('data-aria-one-result');
|
|
57
58
|
this.ariaNResults = ariaNResults || context.getAttribute('data-aria-n-results');
|
|
@@ -66,7 +67,6 @@ export default class AutosuggestUI {
|
|
|
66
67
|
this.typeMore = typeMore || context.getAttribute('data-type-more');
|
|
67
68
|
this.allowMultiple = context.getAttribute('data-allow-multiple') || false;
|
|
68
69
|
this.listboxId = this.listbox.getAttribute('id');
|
|
69
|
-
this.minChars = minChars || 3;
|
|
70
70
|
this.resultLimit = resultLimit || 10;
|
|
71
71
|
this.suggestOnBoot = suggestOnBoot;
|
|
72
72
|
this.lang = lang || 'en';
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
{% endif %}
|
|
41
41
|
{% if checkbox.other is defined and checkbox.other %}
|
|
42
42
|
{% set otherClass = " ons-js-other" %}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{%
|
|
43
|
+
{% if checkbox.other.selectAllChildren is defined and checkbox.other.selectAllChildren == true %}
|
|
44
|
+
{% set otherClass = otherClass + " ons-js-select-all-children" %}
|
|
45
|
+
{% endif %}
|
|
46
46
|
{% endif %}
|
|
47
47
|
<span class="ons-checkboxes__item{{ " ons-checkboxes__item--no-border" if params.borderless }}">
|
|
48
48
|
{% set config = {
|
|
@@ -17,7 +17,7 @@ $collapsible-caret-width: 1.5rem;
|
|
|
17
17
|
|
|
18
18
|
&:focus {
|
|
19
19
|
.ons-collapsible__title {
|
|
20
|
-
@extend a
|
|
20
|
+
@extend %a-focus;
|
|
21
21
|
// extend collapsible focus background behind caret
|
|
22
22
|
margin-left: -$collapsible-caret-width;
|
|
23
23
|
padding-left: $collapsible-caret-width;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
%ons-input-focus {
|
|
2
|
+
box-shadow: 0 0 0 3px $color-focus, inset 0 0 0 1px $color-input;
|
|
3
|
+
outline: none;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
.ons-input {
|
|
2
7
|
border: $input-border-width solid $color-input;
|
|
3
8
|
border-radius: $input-radius;
|
|
@@ -31,8 +36,7 @@
|
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
&:focus {
|
|
34
|
-
|
|
35
|
-
outline: none;
|
|
39
|
+
@extend %ons-input-focus;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
&:disabled {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{% macro onsModal(params) %}
|
|
2
|
+
{% set modalID = params.id | default('dialog') %}
|
|
3
|
+
<dialog class="ons-modal ons-js-modal {{ params.classes }}"
|
|
4
|
+
id="{{ modalID }}"
|
|
5
|
+
role="dialog"
|
|
6
|
+
aria-labelledby="ons-modal-title"
|
|
7
|
+
{% if params.attributes is defined and params.attributes %}{% for attribute, value in (params.attributes.items() if params.attributes is mapping and params.attributes.items else params.attributes) %} {{attribute}}="{{value}}"{% endfor %}{% endif %}
|
|
8
|
+
>
|
|
9
|
+
<h1 id="ons-modal-title" class="ons-modal__title">
|
|
10
|
+
{{ params.title }}
|
|
11
|
+
</h1>
|
|
12
|
+
<div class="ons-modal__body">
|
|
13
|
+
{{ (params.body if params else "") | safe }}{{ caller() if caller }}
|
|
14
|
+
</div>
|
|
15
|
+
{% if params.btnText %}
|
|
16
|
+
{% from "components/button/_macro.njk" import onsButton %}
|
|
17
|
+
{{ onsButton({
|
|
18
|
+
"text": params.btnText,
|
|
19
|
+
"classes": "ons-js-modal-btn ons-u-mt-s"
|
|
20
|
+
}) }}
|
|
21
|
+
{% endif %}
|
|
22
|
+
</dialog>
|
|
23
|
+
{% endmacro %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
$backdrop-colour: rgba(0, 0, 0, 0.8);
|
|
2
|
+
|
|
3
|
+
.ons-modal {
|
|
4
|
+
border: none;
|
|
5
|
+
border-radius: 0.4rem;
|
|
6
|
+
box-shadow: 0 0 7px 0 #000;
|
|
7
|
+
display: none;
|
|
8
|
+
margin-left: 2rem;
|
|
9
|
+
margin-right: 2rem;
|
|
10
|
+
max-width: 500px;
|
|
11
|
+
padding: 2rem;
|
|
12
|
+
width: auto;
|
|
13
|
+
|
|
14
|
+
@media screen and (min-width: 600px) {
|
|
15
|
+
margin-left: auto;
|
|
16
|
+
margin-right: auto;
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&::backdrop {
|
|
21
|
+
backdrop-filter: blur(3px);
|
|
22
|
+
background: $backdrop-colour;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
& + .backdrop {
|
|
26
|
+
background: $backdrop-colour;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ons-modal-overlay {
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import domready from '../../js/domready';
|
|
2
|
+
|
|
3
|
+
async function modals() {
|
|
4
|
+
const modals = [...document.querySelectorAll('.ons-js-modal')];
|
|
5
|
+
const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
|
|
6
|
+
|
|
7
|
+
if (modals.length && !timeouts.length) {
|
|
8
|
+
const Modal = (await import('./modal')).default;
|
|
9
|
+
|
|
10
|
+
modals.forEach(component => new Modal(component));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
domready(modals);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import dialogPolyfill from 'dialog-polyfill';
|
|
2
|
+
|
|
3
|
+
const overLayClass = 'ons-modal-overlay';
|
|
4
|
+
|
|
5
|
+
export default class Modal {
|
|
6
|
+
constructor(component) {
|
|
7
|
+
this.component = component;
|
|
8
|
+
this.launcher = document.querySelector(`[data-modal-id=${component.id}]`);
|
|
9
|
+
this.closeButton = component.querySelector('.ons-js-modal-btn');
|
|
10
|
+
this.lastFocusedEl = null;
|
|
11
|
+
|
|
12
|
+
this.initialise();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
initialise() {
|
|
16
|
+
if (!this.dialogSupported()) {
|
|
17
|
+
/* istanbul ignore next */
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (this.launcher) {
|
|
22
|
+
this.launcher.addEventListener('click', this.openDialog.bind(this));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.closeButton) {
|
|
26
|
+
this.closeButton.addEventListener('click', this.closeDialog.bind(this));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dialogSupported() {
|
|
31
|
+
if (typeof HTMLDialogElement === 'function') {
|
|
32
|
+
return true;
|
|
33
|
+
} else {
|
|
34
|
+
try {
|
|
35
|
+
dialogPolyfill.registerDialog(this.component);
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
/* istanbul ignore next */
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
openDialog(event) {
|
|
45
|
+
if (!this.isDialogOpen()) {
|
|
46
|
+
this.component.classList.add('ons-u-db');
|
|
47
|
+
document.querySelector('body').className += ' ' + overLayClass;
|
|
48
|
+
this.makePageContentInert();
|
|
49
|
+
this.saveLastFocusedEl();
|
|
50
|
+
if (event) {
|
|
51
|
+
const modal = document.getElementById(event.target.getAttribute('data-modal-id'));
|
|
52
|
+
modal.showModal();
|
|
53
|
+
} else {
|
|
54
|
+
this.component.showModal();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
saveLastFocusedEl() {
|
|
60
|
+
this.lastFocusedEl = document.activeElement;
|
|
61
|
+
if (!this.lastFocusedEl || this.lastFocusedEl === document.body) {
|
|
62
|
+
this.lastFocusedEl = null;
|
|
63
|
+
} else if (document.querySelector) {
|
|
64
|
+
this.lastFocusedEl = document.querySelector(':focus');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setFocusOnLastFocusedEl(lastFocusedEl) {
|
|
69
|
+
if (lastFocusedEl) {
|
|
70
|
+
lastFocusedEl.focus();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
makePageContentInert() {
|
|
75
|
+
if (document.querySelector('.ons-page')) {
|
|
76
|
+
document.querySelector('.ons-page').inert = true;
|
|
77
|
+
document.querySelector('.ons-page').setAttribute('aria-hidden', 'true');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
removeInertFromPageContent() {
|
|
82
|
+
if (document.querySelector('.ons-page')) {
|
|
83
|
+
document.querySelector('.ons-page').inert = false;
|
|
84
|
+
document.querySelector('.ons-page').setAttribute('aria-hidden', 'false');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
isDialogOpen() {
|
|
89
|
+
return this.component['open'];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
closeDialog(event) {
|
|
93
|
+
if (this.isDialogOpen()) {
|
|
94
|
+
if (event) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
}
|
|
97
|
+
this.component.classList.remove('ons-u-db');
|
|
98
|
+
document.querySelector('body').classList.remove(overLayClass);
|
|
99
|
+
this.component.close();
|
|
100
|
+
this.setFocusOnLastFocusedEl(this.lastFocusedEl);
|
|
101
|
+
this.removeInertFromPageContent();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@use 'sass:math';
|
|
2
|
+
|
|
1
3
|
$summary-row-spacing: 1rem;
|
|
2
4
|
$summary-col-spacing: 1rem;
|
|
3
5
|
$hub-row-spacing: 1.3rem;
|
|
@@ -101,8 +103,8 @@ $hub-row-spacing: 1.3rem;
|
|
|
101
103
|
padding-right: $summary-col-spacing;
|
|
102
104
|
|
|
103
105
|
@include mq('s') {
|
|
104
|
-
padding-left: $summary-col-spacing
|
|
105
|
-
padding-right: $summary-col-spacing
|
|
106
|
+
padding-left: math.div($summary-col-spacing, 2);
|
|
107
|
+
padding-right: math.div($summary-col-spacing, 2);
|
|
106
108
|
|
|
107
109
|
&:first-child {
|
|
108
110
|
padding-left: $summary-col-spacing;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{% from "components/modal/_macro.njk" import onsModal %}
|
|
2
|
+
{% macro onsTimeoutModal(params) %}
|
|
3
|
+
{% call onsModal({
|
|
4
|
+
"title": params.title,
|
|
5
|
+
"btnText": params.btnText,
|
|
6
|
+
"classes": "ons-js-timeout-modal",
|
|
7
|
+
"attributes": {
|
|
8
|
+
"data-redirect-url": params.redirectUrl,
|
|
9
|
+
"data-timeout-time": params.serverTimeoutTime,
|
|
10
|
+
"data-show-modal-time": params.showModalTimeInSeconds,
|
|
11
|
+
"data-server-session-expiry-endpoint": params.serverSessionExpiryEndpoint,
|
|
12
|
+
"data-countdown-text": params.countdownText,
|
|
13
|
+
"data-redirecting-text": params.redirectingText,
|
|
14
|
+
"data-minutes-text-singular": params.minutesTextSingular,
|
|
15
|
+
"data-minutes-text-plural": params.minutesTextPlural,
|
|
16
|
+
"data-seconds-text-singular": params.secondsTextSingular,
|
|
17
|
+
"data-seconds-text-plural": params.secondsTextPlural,
|
|
18
|
+
"aria-describedby": "timeout-time-remaining"
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
%}
|
|
22
|
+
<p>{{ params.textFirstLine }}</p>
|
|
23
|
+
<p class="ons-js-timeout-timer" aria-hidden="true" aria-relevant="additions"></p>
|
|
24
|
+
<p class="ons-js-timeout-timer-acc ons-u-vh" role="status" id="timeout-time-remaining"></p>
|
|
25
|
+
{% endcall %}
|
|
26
|
+
{% endmacro %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import domready from '../../js/domready';
|
|
2
|
+
|
|
3
|
+
async function modals() {
|
|
4
|
+
const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
|
|
5
|
+
|
|
6
|
+
if (timeouts.length) {
|
|
7
|
+
const Timeout = (await import('./timeout')).default;
|
|
8
|
+
|
|
9
|
+
timeouts.forEach(context => {
|
|
10
|
+
let url = context.getAttribute('data-server-session-expiry-endpoint');
|
|
11
|
+
new Timeout(context, url);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
domready(modals);
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import Modal from '../modal/modal';
|
|
2
|
+
|
|
3
|
+
export default class Timeout {
|
|
4
|
+
constructor(context, url, time) {
|
|
5
|
+
this.context = context;
|
|
6
|
+
this.sessionExpiryEndpoint = url;
|
|
7
|
+
this.initialExpiryTime = time;
|
|
8
|
+
this.continueButton = context.querySelector('.ons-js-modal-btn');
|
|
9
|
+
this.countdown = context.querySelector('.ons-js-timeout-timer');
|
|
10
|
+
this.accessibleCountdown = context.querySelector('.ons-js-timeout-timer-acc');
|
|
11
|
+
this.timeOutRedirectUrl = context.getAttribute('data-redirect-url');
|
|
12
|
+
this.modalVisibleInMilliseconds = context.getAttribute('data-show-modal-time') * 1000;
|
|
13
|
+
|
|
14
|
+
// Language dependent text strings
|
|
15
|
+
this.minutesTextSingular = context.getAttribute('data-minutes-text-singular');
|
|
16
|
+
this.minutesTextPlural = context.getAttribute('data-minutes-text-plural');
|
|
17
|
+
this.secondsTextSingular = context.getAttribute('data-seconds-text-singular');
|
|
18
|
+
this.secondsTextPlural = context.getAttribute('data-seconds-text-plural');
|
|
19
|
+
this.countdownText = context.getAttribute('data-countdown-text');
|
|
20
|
+
this.redirectingText = context.getAttribute('data-redirecting-text');
|
|
21
|
+
|
|
22
|
+
// Settings
|
|
23
|
+
this.expiryTimeInMilliseconds = 0;
|
|
24
|
+
this.expiryTime = '';
|
|
25
|
+
this.showModalTimeout = null;
|
|
26
|
+
this.timers = [];
|
|
27
|
+
this.timerRunOnce = false;
|
|
28
|
+
|
|
29
|
+
// Create modal instance
|
|
30
|
+
this.modal = new Modal(this.context);
|
|
31
|
+
|
|
32
|
+
// Start module
|
|
33
|
+
this.initialise();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async initialise() {
|
|
37
|
+
this.expiryTime = await this.setNewExpiryTime();
|
|
38
|
+
this.expiryTimeInMilliseconds = this.convertTimeToMilliSeconds(this.expiryTime);
|
|
39
|
+
|
|
40
|
+
this.bindEventListeners();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bindEventListeners() {
|
|
44
|
+
window.onload = this.startTimeout();
|
|
45
|
+
window.addEventListener('focus', this.handleWindowFocus.bind(this));
|
|
46
|
+
window.addEventListener('keydown', this.escToClose.bind(this));
|
|
47
|
+
this.continueButton.addEventListener('click', this.closeModalAndRestartTimeout.bind(this));
|
|
48
|
+
this.addThrottledEvents();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
startTimeout() {
|
|
52
|
+
clearTimeout(this.showModalTimeout);
|
|
53
|
+
this.showModalTimeout = setTimeout(this.openModal.bind(this), this.expiryTimeInMilliseconds - this.modalVisibleInMilliseconds);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async openModal() {
|
|
57
|
+
const modalWillOpen = await this.hasExpiryTimeResetInAnotherTab();
|
|
58
|
+
if (modalWillOpen && !this.modal.isDialogOpen()) {
|
|
59
|
+
this.modal.openDialog();
|
|
60
|
+
this.startUiCountdown();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async startUiCountdown() {
|
|
65
|
+
this.clearTimers();
|
|
66
|
+
clearInterval(this.shouldModalCloseCheck);
|
|
67
|
+
|
|
68
|
+
this.shouldModalCloseCheck = setInterval(async () => {
|
|
69
|
+
await this.hasExpiryTimeResetInAnotherTab();
|
|
70
|
+
}, 20000);
|
|
71
|
+
|
|
72
|
+
let millseconds = this.modalVisibleInMilliseconds;
|
|
73
|
+
let seconds = millseconds / 1000;
|
|
74
|
+
let timers = this.timers;
|
|
75
|
+
let $this = this;
|
|
76
|
+
|
|
77
|
+
(async function runTimer() {
|
|
78
|
+
const minutesLeft = parseInt(seconds / 60, 10);
|
|
79
|
+
const secondsLeft = parseInt(seconds % 60, 10);
|
|
80
|
+
const timerExpired = minutesLeft < 1 && secondsLeft < 1;
|
|
81
|
+
|
|
82
|
+
const minutesText =
|
|
83
|
+
minutesLeft + ' ' + (minutesLeft >= 2 ? $this.minutesTextPlural : minutesLeft === 1 ? $this.minutesTextSingular : '');
|
|
84
|
+
const secondsText =
|
|
85
|
+
secondsLeft + ' ' + (secondsLeft >= 2 ? $this.secondsTextPlural : secondsLeft === 1 ? $this.secondsTextSingular : '');
|
|
86
|
+
|
|
87
|
+
let timeLeftText =
|
|
88
|
+
$this.countdownText +
|
|
89
|
+
' <span class="ons-u-fw-b">' +
|
|
90
|
+
(minutesLeft > 0 ? minutesText : '') +
|
|
91
|
+
(minutesLeft > 0 && secondsLeft > 0 ? ' ' : '') +
|
|
92
|
+
(secondsLeft > 0 ? secondsText : '') +
|
|
93
|
+
'</span>.';
|
|
94
|
+
|
|
95
|
+
if (timerExpired) {
|
|
96
|
+
const shouldExpire = await $this.hasExpiryTimeResetInAnotherTab();
|
|
97
|
+
|
|
98
|
+
if (shouldExpire) {
|
|
99
|
+
$this.countdown.innerHTML = '<span class="ons-u-fw-b">' + $this.redirectingText + '</span>';
|
|
100
|
+
$this.accessibleCountdown.innerHTML = $this.redirectingText;
|
|
101
|
+
setTimeout($this.redirect.bind($this), 2000);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
seconds--;
|
|
105
|
+
$this.expiryTimeInMilliseconds = seconds * 1000;
|
|
106
|
+
$this.countdown.innerHTML = timeLeftText;
|
|
107
|
+
|
|
108
|
+
if (minutesLeft < 1 && secondsLeft < 20) {
|
|
109
|
+
$this.accessibleCountdown.setAttribute('aria-live', 'assertive');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!$this.timerRunOnce) {
|
|
113
|
+
$this.accessibleCountdown.innerHTML = timeLeftText;
|
|
114
|
+
$this.timerRunOnce = true;
|
|
115
|
+
} else if (secondsLeft % 15 === 0) {
|
|
116
|
+
$this.accessibleCountdown.innerHTML = timeLeftText;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
timers.push(setTimeout(runTimer.bind($this), 1000));
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async hasExpiryTimeResetInAnotherTab() {
|
|
125
|
+
const checkExpiryTime = await this.getExpiryTime();
|
|
126
|
+
|
|
127
|
+
if (checkExpiryTime != this.expiryTime) {
|
|
128
|
+
this.expiryTime = checkExpiryTime;
|
|
129
|
+
this.expiryTimeInMilliseconds = this.convertTimeToMilliSeconds(checkExpiryTime);
|
|
130
|
+
this.closeModalAndRestartTimeout(this.expiryTimeInMilliseconds);
|
|
131
|
+
} else {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
closeModalAndRestartTimeout(timeInMilliSeconds) {
|
|
137
|
+
if (typeof timeInMilliSeconds !== 'string') {
|
|
138
|
+
timeInMilliSeconds = false;
|
|
139
|
+
}
|
|
140
|
+
if (this.modal.isDialogOpen()) {
|
|
141
|
+
this.modal.closeDialog();
|
|
142
|
+
}
|
|
143
|
+
this.restartTimeout(timeInMilliSeconds);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async restartTimeout(timeInMilliSeconds) {
|
|
147
|
+
this.clearTimers();
|
|
148
|
+
clearInterval(this.shouldModalCloseCheck);
|
|
149
|
+
|
|
150
|
+
if (timeInMilliSeconds) {
|
|
151
|
+
this.expiryTimeInMilliseconds = timeInMilliSeconds;
|
|
152
|
+
} else {
|
|
153
|
+
const createNewExpiryTime = await this.setNewExpiryTime();
|
|
154
|
+
this.expiryTime = createNewExpiryTime;
|
|
155
|
+
this.expiryTimeInMilliseconds = this.convertTimeToMilliSeconds(createNewExpiryTime);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.startTimeout();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async handleWindowFocus() {
|
|
162
|
+
if (this.sessionExpiryEndpoint) {
|
|
163
|
+
const canSetNewExpiry = await this.setNewExpiryTime();
|
|
164
|
+
if (!canSetNewExpiry) {
|
|
165
|
+
this.redirect();
|
|
166
|
+
} else {
|
|
167
|
+
this.closeModalAndRestartTimeout();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
escToClose(event) {
|
|
173
|
+
if (this.modal.isDialogOpen() && event.keyCode === 27) {
|
|
174
|
+
this.closeModalAndRestartTimeout();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async setNewExpiryTime() {
|
|
179
|
+
let newExpiryTime;
|
|
180
|
+
if (!this.sessionExpiryEndpoint) {
|
|
181
|
+
// For demo purposes
|
|
182
|
+
const demoTime = new Date(this.initialExpiryTime ? this.initialExpiryTime : Date.now() + 60 * 1000);
|
|
183
|
+
newExpiryTime = new Date(demoTime).toISOString();
|
|
184
|
+
} else {
|
|
185
|
+
newExpiryTime = await this.fetchExpiryTime('PATCH');
|
|
186
|
+
}
|
|
187
|
+
return newExpiryTime;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getExpiryTime() {
|
|
191
|
+
if (this.sessionExpiryEndpoint) {
|
|
192
|
+
const currentExpiryTime = await this.fetchExpiryTime('GET');
|
|
193
|
+
return currentExpiryTime;
|
|
194
|
+
} else {
|
|
195
|
+
// For demo purposes
|
|
196
|
+
return this.expiryTime;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async fetchExpiryTime(fetchMethod) {
|
|
201
|
+
let response = await fetch(this.sessionExpiryEndpoint, {
|
|
202
|
+
method: fetchMethod,
|
|
203
|
+
headers: { 'Cache-Control': 'no-cache', 'Content-type': 'application/json; charset=UTF-8' },
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
this.redirect();
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let json = await response.json();
|
|
211
|
+
|
|
212
|
+
return json.expires_at;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
convertTimeToMilliSeconds(expiryTime) {
|
|
216
|
+
const time = new Date(expiryTime);
|
|
217
|
+
const calculateTimeInMilliSeconds = Math.abs(time - new Date());
|
|
218
|
+
|
|
219
|
+
return calculateTimeInMilliSeconds;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
redirect() {
|
|
223
|
+
window.location.replace(this.timeOutRedirectUrl);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
clearTimers() {
|
|
227
|
+
for (let i = 0; i < this.timers.length; i++) {
|
|
228
|
+
clearTimeout(this.timers[i]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
addThrottledEvents() {
|
|
233
|
+
window.onclick = this.throttle(() => {
|
|
234
|
+
/* istanbul ignore next */
|
|
235
|
+
if (!this.modal.isDialogOpen()) {
|
|
236
|
+
this.restartTimeout();
|
|
237
|
+
}
|
|
238
|
+
}, 61000);
|
|
239
|
+
|
|
240
|
+
window.onmousemove = this.throttle(() => {
|
|
241
|
+
/* istanbul ignore next */
|
|
242
|
+
if (!this.modal.isDialogOpen()) {
|
|
243
|
+
this.restartTimeout();
|
|
244
|
+
}
|
|
245
|
+
}, 61000);
|
|
246
|
+
|
|
247
|
+
window.onmousedown = this.throttle(() => {
|
|
248
|
+
/* istanbul ignore next */
|
|
249
|
+
if (!this.modal.isDialogOpen()) {
|
|
250
|
+
this.restartTimeout();
|
|
251
|
+
}
|
|
252
|
+
}, 61000);
|
|
253
|
+
|
|
254
|
+
window.onscroll = this.throttle(() => {
|
|
255
|
+
/* istanbul ignore next */
|
|
256
|
+
if (!this.modal.isDialogOpen()) {
|
|
257
|
+
this.restartTimeout();
|
|
258
|
+
}
|
|
259
|
+
}, 61000);
|
|
260
|
+
|
|
261
|
+
window.onkeypress = this.throttle(() => {
|
|
262
|
+
/* istanbul ignore next */
|
|
263
|
+
if (!this.modal.isDialogOpen()) {
|
|
264
|
+
this.restartTimeout();
|
|
265
|
+
}
|
|
266
|
+
}, 61000);
|
|
267
|
+
|
|
268
|
+
window.onkeyup = this.throttle(() => {
|
|
269
|
+
/* istanbul ignore next */
|
|
270
|
+
if (!this.modal.isDialogOpen()) {
|
|
271
|
+
this.restartTimeout();
|
|
272
|
+
}
|
|
273
|
+
}, 61000);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
throttle(func, wait) {
|
|
277
|
+
let waiting = false;
|
|
278
|
+
return function() {
|
|
279
|
+
if (waiting) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
waiting = true;
|
|
283
|
+
setTimeout(async () => {
|
|
284
|
+
func.apply(this, arguments);
|
|
285
|
+
waiting = false;
|
|
286
|
+
}, wait);
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|