@4site/engrid-scripts 0.25.9 → 0.25.12
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/dist/a11y.d.ts +26 -4
- package/dist/a11y.js +70 -15
- package/dist/ecard.d.ts +4 -0
- package/dist/ecard.js +196 -1
- package/dist/interfaces/options.d.ts +3 -0
- package/dist/remember-me.d.ts +65 -0
- package/dist/remember-me.js +323 -17
- package/dist/supporter-hub.d.ts +41 -1
- package/dist/supporter-hub.js +227 -39
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/a11y.d.ts
CHANGED
|
@@ -2,10 +2,19 @@ export declare class A11y {
|
|
|
2
2
|
private logger;
|
|
3
3
|
private observer;
|
|
4
4
|
constructor();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Apply the field-level accessibility tagging (error alert live regions,
|
|
7
|
+
* aria-required, fallback aria-labels, radio group roles) to every field
|
|
8
|
+
* within `root`. Defaults to the whole document on initial load, but can be
|
|
9
|
+
* pointed at a freshly injected fragment (e.g. a Supporter Hub overlay) so
|
|
10
|
+
* dynamically added forms get the same treatment. All operations are
|
|
11
|
+
* idempotent, so re-scanning already tagged fields is safe.
|
|
12
|
+
*/
|
|
13
|
+
static scanFields(root?: ParentNode): void;
|
|
14
|
+
private static addErrorAlertArea;
|
|
15
|
+
private static addGroupRole;
|
|
16
|
+
private static addRequired;
|
|
17
|
+
private static addLabel;
|
|
9
18
|
private updateFrequencyLabel;
|
|
10
19
|
private setAutoGeneratedAltTags;
|
|
11
20
|
/**
|
|
@@ -18,5 +27,18 @@ export declare class A11y {
|
|
|
18
27
|
private observeErrorMessages;
|
|
19
28
|
private moveErrorMessage;
|
|
20
29
|
private clearErrorMessage;
|
|
30
|
+
/**
|
|
31
|
+
* Make everything on the page inert except the supplied overlay element and
|
|
32
|
+
* its ancestors. This hides background content from assistive technology and
|
|
33
|
+
* prevents focus from escaping a modal-style overlay.
|
|
34
|
+
*
|
|
35
|
+
* @param inert When true, set `inert` on all siblings of the overlay and of
|
|
36
|
+
* each of its ancestors. When false, remove `inert` from every
|
|
37
|
+
* element this method previously marked (tracked via the
|
|
38
|
+
* `data-engrid-inert` flag).
|
|
39
|
+
* @param overlay The element that should remain interactive. Required when
|
|
40
|
+
* `inert` is true; ignored when `inert` is false.
|
|
41
|
+
*/
|
|
42
|
+
static inertPage(inert: boolean, overlay?: HTMLElement): void;
|
|
21
43
|
private manageErrorListAlertRole;
|
|
22
44
|
}
|
package/dist/a11y.js
CHANGED
|
@@ -5,21 +5,32 @@ export class A11y {
|
|
|
5
5
|
constructor() {
|
|
6
6
|
this.logger = new EngridLogger("A11y", "#FFFFFF", "#811212", "👁️🗨️");
|
|
7
7
|
this.observer = null;
|
|
8
|
-
|
|
9
|
-
this.addRequired();
|
|
10
|
-
this.addLabel();
|
|
11
|
-
this.addGroupRole();
|
|
8
|
+
A11y.scanFields();
|
|
12
9
|
this.updateFrequencyLabel();
|
|
13
10
|
const ecardImages = document.querySelectorAll('.en__ecarditems__list img');
|
|
14
11
|
this.setAutoGeneratedAltTags(ecardImages);
|
|
15
12
|
this.manageErrorListAlertRole();
|
|
16
13
|
this.observeErrorMessages();
|
|
17
14
|
}
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Apply the field-level accessibility tagging (error alert live regions,
|
|
17
|
+
* aria-required, fallback aria-labels, radio group roles) to every field
|
|
18
|
+
* within `root`. Defaults to the whole document on initial load, but can be
|
|
19
|
+
* pointed at a freshly injected fragment (e.g. a Supporter Hub overlay) so
|
|
20
|
+
* dynamically added forms get the same treatment. All operations are
|
|
21
|
+
* idempotent, so re-scanning already tagged fields is safe.
|
|
22
|
+
*/
|
|
23
|
+
static scanFields(root = document) {
|
|
24
|
+
A11y.addErrorAlertArea(root);
|
|
25
|
+
A11y.addRequired(root);
|
|
26
|
+
A11y.addLabel(root);
|
|
27
|
+
A11y.addGroupRole(root);
|
|
28
|
+
}
|
|
29
|
+
static addErrorAlertArea(root = document) {
|
|
30
|
+
const fieldElements = root.querySelectorAll('.en__field .en__field__element');
|
|
20
31
|
fieldElements.forEach((fieldElement) => {
|
|
21
|
-
|
|
22
|
-
if (
|
|
32
|
+
const fieldWrapper = fieldElement.closest('.en__field');
|
|
33
|
+
if (fieldWrapper === null || fieldWrapper === void 0 ? void 0 : fieldWrapper.querySelector('.en__field__error__alert'))
|
|
23
34
|
return;
|
|
24
35
|
const errorAlert = document.createElement('div');
|
|
25
36
|
errorAlert.setAttribute('aria-live', 'polite');
|
|
@@ -29,10 +40,13 @@ export class A11y {
|
|
|
29
40
|
fieldElement.insertAdjacentElement('afterend', errorAlert);
|
|
30
41
|
});
|
|
31
42
|
}
|
|
32
|
-
addGroupRole() {
|
|
43
|
+
static addGroupRole(root = document) {
|
|
33
44
|
// Add role="group" to all EN Radio fields
|
|
34
|
-
const radioFields =
|
|
45
|
+
const radioFields = root.querySelectorAll(".en__field--radio");
|
|
35
46
|
radioFields.forEach((field) => {
|
|
47
|
+
// Skip fields already tagged so re-scans don't regenerate label IDs.
|
|
48
|
+
if (field.getAttribute("role") === "group")
|
|
49
|
+
return;
|
|
36
50
|
field.setAttribute("role", "group");
|
|
37
51
|
// Add random ID to the label
|
|
38
52
|
const label = field.querySelector("label");
|
|
@@ -42,19 +56,19 @@ export class A11y {
|
|
|
42
56
|
}
|
|
43
57
|
});
|
|
44
58
|
}
|
|
45
|
-
addRequired() {
|
|
46
|
-
const mandatoryFields =
|
|
59
|
+
static addRequired(root = document) {
|
|
60
|
+
const mandatoryFields = root.querySelectorAll(".en__mandatory .en__field__input");
|
|
47
61
|
mandatoryFields.forEach((field) => {
|
|
48
62
|
field.setAttribute("aria-required", "true");
|
|
49
63
|
});
|
|
50
64
|
}
|
|
51
|
-
addLabel() {
|
|
52
|
-
const otherAmount =
|
|
65
|
+
static addLabel(root = document) {
|
|
66
|
+
const otherAmount = root.querySelector(".en__field__input--otheramount");
|
|
53
67
|
if (otherAmount) {
|
|
54
68
|
otherAmount.setAttribute("aria-label", "Enter your custom donation amount");
|
|
55
69
|
}
|
|
56
70
|
// Split selects usually don't have a label, so let's make the first option the label
|
|
57
|
-
const splitSelects =
|
|
71
|
+
const splitSelects = root.querySelectorAll(".en__field__input--splitselect");
|
|
58
72
|
splitSelects.forEach((select) => {
|
|
59
73
|
var _a, _b, _c, _d;
|
|
60
74
|
const firstOption = select.querySelector("option");
|
|
@@ -204,6 +218,47 @@ export class A11y {
|
|
|
204
218
|
inputElement.removeAttribute('aria-describedby');
|
|
205
219
|
}
|
|
206
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Make everything on the page inert except the supplied overlay element and
|
|
223
|
+
* its ancestors. This hides background content from assistive technology and
|
|
224
|
+
* prevents focus from escaping a modal-style overlay.
|
|
225
|
+
*
|
|
226
|
+
* @param inert When true, set `inert` on all siblings of the overlay and of
|
|
227
|
+
* each of its ancestors. When false, remove `inert` from every
|
|
228
|
+
* element this method previously marked (tracked via the
|
|
229
|
+
* `data-engrid-inert` flag).
|
|
230
|
+
* @param overlay The element that should remain interactive. Required when
|
|
231
|
+
* `inert` is true; ignored when `inert` is false.
|
|
232
|
+
*/
|
|
233
|
+
static inertPage(inert, overlay) {
|
|
234
|
+
if (inert) {
|
|
235
|
+
if (!overlay)
|
|
236
|
+
return;
|
|
237
|
+
let element = overlay;
|
|
238
|
+
while (element && element !== document.body) {
|
|
239
|
+
const parent = element.parentElement;
|
|
240
|
+
if (parent) {
|
|
241
|
+
Array.from(parent.children).forEach((sibling) => {
|
|
242
|
+
if (sibling !== element &&
|
|
243
|
+
sibling instanceof HTMLElement &&
|
|
244
|
+
!sibling.hasAttribute("inert")) {
|
|
245
|
+
sibling.setAttribute("inert", "");
|
|
246
|
+
sibling.dataset.engridInert = "true";
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
element = parent;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
document
|
|
255
|
+
.querySelectorAll("[data-engrid-inert]")
|
|
256
|
+
.forEach((element) => {
|
|
257
|
+
element.removeAttribute("inert");
|
|
258
|
+
delete element.dataset.engridInert;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
207
262
|
manageErrorListAlertRole() {
|
|
208
263
|
const errorList = document.querySelector('ul.en__errorList');
|
|
209
264
|
if (!errorList)
|
package/dist/ecard.d.ts
CHANGED
package/dist/ecard.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ENGrid, EnForm, EngridLogger } from ".";
|
|
1
|
+
import { ENGrid, EnForm, EngridLogger, A11y } from ".";
|
|
2
2
|
export class Ecard {
|
|
3
3
|
constructor() {
|
|
4
4
|
this._form = EnForm.getInstance();
|
|
5
5
|
this.logger = new EngridLogger("Ecard", "red", "#f5f5f5", "🪪");
|
|
6
6
|
if (!this.shouldRun())
|
|
7
7
|
return;
|
|
8
|
+
this.altsAndArias();
|
|
8
9
|
this._form.onValidate.subscribe(() => this.checkRecipientFields());
|
|
9
10
|
const schedule = ENGrid.getUrlParameter("engrid_ecard.schedule");
|
|
10
11
|
const scheduleField = ENGrid.getField("ecard.schedule");
|
|
@@ -58,4 +59,198 @@ export class Ecard {
|
|
|
58
59
|
}
|
|
59
60
|
return true;
|
|
60
61
|
}
|
|
62
|
+
altsAndArias() {
|
|
63
|
+
document.querySelectorAll(".en__ecarditems__list").forEach((list) => {
|
|
64
|
+
this.altsAndAriasEcardItemsList(list);
|
|
65
|
+
});
|
|
66
|
+
const ecardMessage = document.querySelector(".en__ecardmessage");
|
|
67
|
+
if (ecardMessage) {
|
|
68
|
+
this.coupleH2AndInput(ecardMessage, "Add a Message to your eCard");
|
|
69
|
+
}
|
|
70
|
+
const ecardRecipients = document.querySelector(".en__ecardrecipients");
|
|
71
|
+
if (ecardRecipients) {
|
|
72
|
+
const recipientName = ecardRecipients.querySelector(".en__ecardrecipients__name");
|
|
73
|
+
if (recipientName) {
|
|
74
|
+
this.coupleLabelAndInput(recipientName, "Recipient Name");
|
|
75
|
+
}
|
|
76
|
+
const recipientEmail = ecardRecipients.querySelector(".en__ecardrecipients__email");
|
|
77
|
+
if (recipientEmail) {
|
|
78
|
+
this.coupleLabelAndInput(recipientEmail, "Recipient Email");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const ecardFutureDelivery = document.querySelector(".en__ecardrecipients__futureDelivery");
|
|
82
|
+
if (ecardFutureDelivery) {
|
|
83
|
+
this.coupleH2AndInput(ecardFutureDelivery, "Schedule your eCard for future delivery");
|
|
84
|
+
}
|
|
85
|
+
const previewButton = document.querySelector(".en__ecarditems__showprev");
|
|
86
|
+
if (previewButton) {
|
|
87
|
+
previewButton.setAttribute("aria-controls", "ecard-preview");
|
|
88
|
+
previewButton.setAttribute("aria-haspopup", "dialog");
|
|
89
|
+
}
|
|
90
|
+
const previewModal = document.querySelector(".en__ecarditems__preview");
|
|
91
|
+
if (previewModal) {
|
|
92
|
+
previewModal.setAttribute("role", "dialog");
|
|
93
|
+
previewModal.setAttribute("aria-modal", "true");
|
|
94
|
+
previewModal.setAttribute("aria-label", "Ecard Preview Modal");
|
|
95
|
+
previewModal.setAttribute("id", "ecard-preview");
|
|
96
|
+
const closeButton = previewModal.querySelector(".en__ecarditems__prevclose");
|
|
97
|
+
if (closeButton) {
|
|
98
|
+
closeButton.setAttribute("role", "button");
|
|
99
|
+
closeButton.setAttribute("aria-label", "Close Preview");
|
|
100
|
+
document.addEventListener("keydown", (e) => {
|
|
101
|
+
if (e.key === "Escape" &&
|
|
102
|
+
previewModal.classList.contains("preview--show")) {
|
|
103
|
+
closeButton.click();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const iframe = previewModal.querySelector("iframe");
|
|
108
|
+
if (iframe) {
|
|
109
|
+
iframe.setAttribute("title", "Ecard Preview Frame");
|
|
110
|
+
}
|
|
111
|
+
const observer = new MutationObserver((mutations) => {
|
|
112
|
+
mutations.forEach((mutation) => {
|
|
113
|
+
if (mutation.type === "attributes" &&
|
|
114
|
+
mutation.attributeName === "class") {
|
|
115
|
+
const target = mutation.target;
|
|
116
|
+
if (target.classList.contains("preview--show")) {
|
|
117
|
+
A11y.inertPage(true, previewModal);
|
|
118
|
+
// Focus the iframe or the first focusable element in the modal
|
|
119
|
+
const focusableElements = previewModal.querySelectorAll('iframe, a[href], area[href], button:not([disabled]), object, embed, [tabindex="0"]');
|
|
120
|
+
if (focusableElements.length) {
|
|
121
|
+
focusableElements[0].focus();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
A11y.inertPage(false);
|
|
126
|
+
// Return focus to the preview button
|
|
127
|
+
if (previewButton) {
|
|
128
|
+
previewButton.focus();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
observer.observe(previewModal, {
|
|
135
|
+
attributes: true,
|
|
136
|
+
attributeFilter: ["class"],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
altsAndAriasEcardItemsList(list) {
|
|
141
|
+
// if there's a sibling h2, use its text as the aria-label for the list
|
|
142
|
+
const h2 = list.previousElementSibling;
|
|
143
|
+
if (h2 && h2.tagName === "H2") {
|
|
144
|
+
const id = `ecard-list-${Math.random().toString(36).substring(2, 9)}`;
|
|
145
|
+
h2.setAttribute("id", id);
|
|
146
|
+
list.setAttribute("aria-labelledby", id);
|
|
147
|
+
}
|
|
148
|
+
list.setAttribute("role", "radiogroup");
|
|
149
|
+
const thumbs = Array.from(list.querySelectorAll(".en__ecarditems__thumb"));
|
|
150
|
+
let isSelection = false;
|
|
151
|
+
thumbs.forEach((thumb, index) => {
|
|
152
|
+
thumb.setAttribute("role", "radio");
|
|
153
|
+
if (thumb.classList.contains("thumb--active")) {
|
|
154
|
+
thumb.setAttribute("aria-checked", "true");
|
|
155
|
+
thumb.setAttribute("tabindex", "0");
|
|
156
|
+
isSelection = true;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
thumb.setAttribute("aria-checked", "false");
|
|
160
|
+
thumb.setAttribute("tabindex", "-1");
|
|
161
|
+
}
|
|
162
|
+
const img = thumb.querySelector("img");
|
|
163
|
+
if (img) {
|
|
164
|
+
thumb.setAttribute("aria-label", img.alt || "Ecard Thumbnail");
|
|
165
|
+
img.setAttribute("aria-hidden", "true");
|
|
166
|
+
}
|
|
167
|
+
// Keyboard navigation (WAI-ARIA radio group pattern)
|
|
168
|
+
thumb.addEventListener("keydown", (e) => {
|
|
169
|
+
let nextIndex = null;
|
|
170
|
+
switch (e.key) {
|
|
171
|
+
case "ArrowRight":
|
|
172
|
+
case "ArrowDown":
|
|
173
|
+
nextIndex = (index + 1) % thumbs.length;
|
|
174
|
+
break;
|
|
175
|
+
case "ArrowLeft":
|
|
176
|
+
case "ArrowUp":
|
|
177
|
+
nextIndex = (index - 1 + thumbs.length) % thumbs.length;
|
|
178
|
+
break;
|
|
179
|
+
case "Home":
|
|
180
|
+
nextIndex = 0;
|
|
181
|
+
break;
|
|
182
|
+
case "End":
|
|
183
|
+
nextIndex = thumbs.length - 1;
|
|
184
|
+
break;
|
|
185
|
+
case "Enter":
|
|
186
|
+
case " ":
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
thumb.click();
|
|
189
|
+
return;
|
|
190
|
+
default:
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
// In a radio group, moving focus also selects the option.
|
|
195
|
+
// click() lets EN's own handler set the value + thumb--active class;
|
|
196
|
+
// the MutationObserver below then syncs aria-checked + tabindex.
|
|
197
|
+
thumbs[nextIndex].focus();
|
|
198
|
+
thumbs[nextIndex].click();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
if (!isSelection && thumbs.length) {
|
|
202
|
+
thumbs[0].setAttribute("tabindex", "0");
|
|
203
|
+
}
|
|
204
|
+
// MutationObserver to watch for "thumb--active" class changes and keep
|
|
205
|
+
// aria-checked + roving tabindex in sync with the selected thumb
|
|
206
|
+
const observer = new MutationObserver((mutations) => {
|
|
207
|
+
mutations.forEach((mutation) => {
|
|
208
|
+
if (mutation.type === "attributes" &&
|
|
209
|
+
mutation.attributeName === "class") {
|
|
210
|
+
const target = mutation.target;
|
|
211
|
+
if (target.classList.contains("thumb--active")) {
|
|
212
|
+
target.setAttribute("aria-checked", "true");
|
|
213
|
+
// Roving tabindex: only the active thumb is tabbable
|
|
214
|
+
target.setAttribute("tabindex", "0");
|
|
215
|
+
thumbs.forEach((t) => {
|
|
216
|
+
if (t !== target)
|
|
217
|
+
t.setAttribute("tabindex", "-1");
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
target.setAttribute("aria-checked", "false");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
observer.observe(list, {
|
|
227
|
+
attributes: true,
|
|
228
|
+
subtree: true,
|
|
229
|
+
attributeFilter: ["class"],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
coupleLabelAndInput(parent, labelText) {
|
|
233
|
+
const label = parent.querySelector("label");
|
|
234
|
+
const input = parent.querySelector("input, textarea, select");
|
|
235
|
+
if (label && input) {
|
|
236
|
+
const id = `ecard-input-${Math.random().toString(36).substring(2, 9)}`;
|
|
237
|
+
label.setAttribute("id", id);
|
|
238
|
+
input.setAttribute("aria-labelledby", id);
|
|
239
|
+
}
|
|
240
|
+
else if (input) {
|
|
241
|
+
input.setAttribute("aria-label", labelText);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
coupleH2AndInput(parent, labelText) {
|
|
245
|
+
const h2 = parent.querySelector("h2");
|
|
246
|
+
const input = parent.querySelector("textarea, input, select");
|
|
247
|
+
if (h2 && input) {
|
|
248
|
+
const id = `ecard-message-${Math.random().toString(36).substring(2, 9)}`;
|
|
249
|
+
h2.setAttribute("id", id);
|
|
250
|
+
input.setAttribute("aria-labelledby", id);
|
|
251
|
+
}
|
|
252
|
+
else if (input) {
|
|
253
|
+
input.setAttribute("aria-label", labelText);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
61
256
|
}
|
|
@@ -50,7 +50,10 @@ export interface Options {
|
|
|
50
50
|
fieldOptInSelectorTargetLocation?: string;
|
|
51
51
|
fieldClearSelectorTarget?: string;
|
|
52
52
|
fieldClearSelectorTargetLocation?: string;
|
|
53
|
+
fieldClearLabel?: string;
|
|
53
54
|
checked?: boolean;
|
|
55
|
+
encryptData?: boolean;
|
|
56
|
+
hide?: boolean;
|
|
54
57
|
};
|
|
55
58
|
StickyNSG?: boolean;
|
|
56
59
|
StickyPrepopulation?: false | {
|
package/dist/remember-me.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { EnForm, RememberMeEvents } from "./events";
|
|
|
2
2
|
export declare class RememberMe {
|
|
3
3
|
_form: EnForm;
|
|
4
4
|
_events: RememberMeEvents;
|
|
5
|
+
private _frequency;
|
|
5
6
|
private remoteUrl;
|
|
6
7
|
private cookieName;
|
|
7
8
|
private fieldNames;
|
|
@@ -9,14 +10,18 @@ export declare class RememberMe {
|
|
|
9
10
|
private cookieExpirationDays;
|
|
10
11
|
private iframe;
|
|
11
12
|
private rememberMeOptIn;
|
|
13
|
+
private encryptData;
|
|
14
|
+
private hide;
|
|
12
15
|
private fieldDonationAmountRadioName;
|
|
13
16
|
private fieldDonationAmountOtherName;
|
|
14
17
|
private fieldDonationRecurrPayRadioName;
|
|
18
|
+
private fieldDonationRecurrFreqRadioName;
|
|
15
19
|
private fieldDonationAmountOtherCheckboxID;
|
|
16
20
|
private fieldOptInSelectorTarget;
|
|
17
21
|
private fieldOptInSelectorTargetLocation;
|
|
18
22
|
private fieldClearSelectorTarget;
|
|
19
23
|
private fieldClearSelectorTargetLocation;
|
|
24
|
+
private fieldClearLabel;
|
|
20
25
|
constructor(options: {
|
|
21
26
|
remoteUrl?: string;
|
|
22
27
|
cookieName?: string;
|
|
@@ -25,12 +30,16 @@ export declare class RememberMe {
|
|
|
25
30
|
fieldDonationAmountRadioName?: string;
|
|
26
31
|
fieldDonationAmountOtherName?: string;
|
|
27
32
|
fieldDonationRecurrPayRadioName?: string;
|
|
33
|
+
fieldDonationRecurrFreqRadioName?: string;
|
|
28
34
|
fieldDonationAmountOtherCheckboxID?: string;
|
|
29
35
|
fieldOptInSelectorTarget?: string;
|
|
30
36
|
fieldOptInSelectorTargetLocation?: string;
|
|
31
37
|
fieldClearSelectorTarget?: string;
|
|
32
38
|
fieldClearSelectorTargetLocation?: string;
|
|
39
|
+
fieldClearLabel?: string;
|
|
33
40
|
checked?: boolean;
|
|
41
|
+
encryptData?: boolean;
|
|
42
|
+
hide?: boolean;
|
|
34
43
|
});
|
|
35
44
|
private updateFieldData;
|
|
36
45
|
private insertClearRememberMeLink;
|
|
@@ -43,6 +52,42 @@ export declare class RememberMe {
|
|
|
43
52
|
private saveCookieToRemote;
|
|
44
53
|
private readCookie;
|
|
45
54
|
private saveCookie;
|
|
55
|
+
/**
|
|
56
|
+
* Reads and decrypts the local (non-remote) Remember Me cookie using
|
|
57
|
+
* browser-native AES-GCM (Web Crypto), with the key held in localStorage
|
|
58
|
+
* on this device. If the key is absent (different device or cleared
|
|
59
|
+
* storage) or decryption otherwise fails, the field data is left empty
|
|
60
|
+
* and the component falls back to the normal, no-autofill experience.
|
|
61
|
+
*/
|
|
62
|
+
private readCookieEncrypted;
|
|
63
|
+
/**
|
|
64
|
+
* Encrypts the current fieldData with AES-GCM (Web Crypto) and stores the
|
|
65
|
+
* base64-encoded result in the local cookie. If encryption isn't possible
|
|
66
|
+
* (e.g. Web Crypto unavailable), nothing is written.
|
|
67
|
+
*/
|
|
68
|
+
private saveCookieEncrypted;
|
|
69
|
+
private clearCookieEncrypted;
|
|
70
|
+
/**
|
|
71
|
+
* Retrieves the per-device AES-GCM encryption key. A random secret
|
|
72
|
+
* generated once per device and held in localStorage — never written
|
|
73
|
+
* to the cookie, so it never travels with the transported value.
|
|
74
|
+
*/
|
|
75
|
+
private getEncryptionKey;
|
|
76
|
+
/**
|
|
77
|
+
* Encrypts a plaintext string with AES-GCM and returns the base64-encoded
|
|
78
|
+
* IV + ciphertext, ready for storage. Returns null if a key isn't
|
|
79
|
+
* available (e.g. Web Crypto unsupported).
|
|
80
|
+
*/
|
|
81
|
+
private encryptPayload;
|
|
82
|
+
/**
|
|
83
|
+
* Decrypts a base64-encoded IV + ciphertext payload previously produced by
|
|
84
|
+
* encryptPayload. Returns null (rather than throwing) if the key is
|
|
85
|
+
* missing or decryption otherwise fails, so callers can gracefully fall
|
|
86
|
+
* back to the standard, no-autofill experience.
|
|
87
|
+
*/
|
|
88
|
+
private decryptPayload;
|
|
89
|
+
private arrayBufferToBase64;
|
|
90
|
+
private base64ToArrayBuffer;
|
|
46
91
|
private readFields;
|
|
47
92
|
private setFieldValue;
|
|
48
93
|
private clearFields;
|
|
@@ -57,5 +102,25 @@ export declare class RememberMe {
|
|
|
57
102
|
* @param overwrite - A boolean indicating whether to overwrite the existing value of the fields. Defaults to false.
|
|
58
103
|
*/
|
|
59
104
|
private writeFields;
|
|
105
|
+
/**
|
|
106
|
+
* SwapAmounts replaces the donationAmt radio DOM nodes ~1 second after page
|
|
107
|
+
* load (triggered by DonationFrequency.load() setTimeout). When that happens
|
|
108
|
+
* the selection the RememberMe just wrote gets wiped out.
|
|
109
|
+
*
|
|
110
|
+
* This method subscribes to the first onFrequencyChange event and, after a
|
|
111
|
+
* short delay to let SwapAmounts finish its DOM update, re-applies only the
|
|
112
|
+
* donation amount. It unsubscribes immediately so it only fires once.
|
|
113
|
+
*
|
|
114
|
+
* To avoid overwriting a manual donor interaction, the handler checks
|
|
115
|
+
* whether the current amount selection is empty/wiped (as SwapAmounts does)
|
|
116
|
+
* OR still matches what writeFields originally set. If the donor already
|
|
117
|
+
* picked a different amount, we skip re-application.
|
|
118
|
+
*/
|
|
119
|
+
private reapplyDonationAmtAfterSwap;
|
|
120
|
+
/**
|
|
121
|
+
* Returns the currently selected donation amount value, or null if nothing
|
|
122
|
+
* is selected. Checks both predefined radio buttons and the "Other" text input.
|
|
123
|
+
*/
|
|
124
|
+
private getCurrentSelectedAmount;
|
|
60
125
|
private isJson;
|
|
61
126
|
}
|