@4site/engrid-scripts 0.27.1 → 0.27.3
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 +18 -10
- package/dist/a11y.js +206 -80
- package/dist/events/donation-frequency.d.ts +3 -0
- package/dist/events/donation-frequency.js +14 -0
- package/dist/frequency-upsell.js +1 -0
- package/dist/interfaces/upsell-options.d.ts +3 -0
- package/dist/interfaces/upsell-options.js +23 -17
- package/dist/page-background.d.ts +5 -0
- package/dist/page-background.js +3 -0
- package/dist/upsell-lightbox.d.ts +3 -1
- package/dist/upsell-lightbox.js +89 -57
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/a11y.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export declare class A11y {
|
|
2
2
|
private logger;
|
|
3
3
|
private observer;
|
|
4
|
+
private liveRegionUpdateTimeout;
|
|
5
|
+
private shouldFocusFirstInvalidField;
|
|
4
6
|
constructor();
|
|
5
7
|
/**
|
|
6
|
-
* Apply the field-level accessibility tagging (error
|
|
8
|
+
* Apply the field-level accessibility tagging (error containers,
|
|
7
9
|
* aria-required, fallback aria-labels, radio group roles) to every field
|
|
8
10
|
* within `root`. Defaults to the whole document on initial load, but can be
|
|
9
11
|
* pointed at a freshly injected fragment (e.g. a Supporter Hub overlay) so
|
|
@@ -11,22 +13,23 @@ export declare class A11y {
|
|
|
11
13
|
* idempotent, so re-scanning already tagged fields is safe.
|
|
12
14
|
*/
|
|
13
15
|
static scanFields(root?: ParentNode): void;
|
|
14
|
-
private static
|
|
16
|
+
private static addGlobalErrorLiveRegion;
|
|
15
17
|
private static addGroupRole;
|
|
16
18
|
private static addRequired;
|
|
17
19
|
private static addLabel;
|
|
18
20
|
private updateFrequencyLabel;
|
|
19
21
|
private setAutoGeneratedAltTags;
|
|
20
22
|
/**
|
|
21
|
-
* Observe #engrid for .en__field__error additions and removals,
|
|
22
|
-
*
|
|
23
|
-
* aria-invalid / aria-describedby on the corresponding input
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Observe #engrid for .en__field__error additions and removals, relocating
|
|
24
|
+
* each error after its field's input so DOM order matches visual order. Also
|
|
25
|
+
* toggles aria-invalid / aria-describedby on the corresponding input (or radio
|
|
26
|
+
* group when the field has role="group") and updates a single, DOM-ordered
|
|
27
|
+
* global live region. Runs for the lifetime of the page so async validators
|
|
28
|
+
* (NeverBounce, VGS, server re-renders) are caught without timing assumptions.
|
|
26
29
|
*/
|
|
27
30
|
private observeErrorMessages;
|
|
28
|
-
private
|
|
29
|
-
private
|
|
31
|
+
private tagFieldError;
|
|
32
|
+
private clearFieldError;
|
|
30
33
|
/**
|
|
31
34
|
* Make everything on the page inert except the supplied overlay element and
|
|
32
35
|
* its ancestors. This hides background content from assistive technology and
|
|
@@ -40,5 +43,10 @@ export declare class A11y {
|
|
|
40
43
|
* `inert` is true; ignored when `inert` is false.
|
|
41
44
|
*/
|
|
42
45
|
static inertPage(inert: boolean, overlay?: HTMLElement): void;
|
|
43
|
-
private
|
|
46
|
+
private scheduleLiveRegionUpdate;
|
|
47
|
+
private updateGlobalErrorLiveRegion;
|
|
48
|
+
private formatErrorMessage;
|
|
49
|
+
private focusFirstInvalidField;
|
|
50
|
+
private getFieldLabel;
|
|
51
|
+
private normalizeLabel;
|
|
44
52
|
}
|
package/dist/a11y.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import { EngridLogger } from "./logger";
|
|
2
|
+
import { ENGrid } from "./engrid";
|
|
3
|
+
import { EnForm } from "./events/en-form";
|
|
2
4
|
// a11y means accessibility
|
|
3
5
|
// This Component is supposed to be used as a helper for Aria Attributes & Other Accessibility Features
|
|
4
6
|
export class A11y {
|
|
5
7
|
constructor() {
|
|
8
|
+
var _a;
|
|
6
9
|
this.logger = new EngridLogger("A11y", "#FFFFFF", "#811212", "👁️🗨️");
|
|
7
10
|
this.observer = null;
|
|
11
|
+
this.liveRegionUpdateTimeout = null;
|
|
12
|
+
this.shouldFocusFirstInvalidField = false;
|
|
8
13
|
A11y.scanFields();
|
|
9
14
|
this.updateFrequencyLabel();
|
|
10
15
|
const ecardImages = document.querySelectorAll('.en__ecarditems__list img');
|
|
11
16
|
this.setAutoGeneratedAltTags(ecardImages);
|
|
12
|
-
this.manageErrorListAlertRole();
|
|
13
17
|
this.observeErrorMessages();
|
|
18
|
+
(_a = ENGrid.enForm) === null || _a === void 0 ? void 0 : _a.addEventListener('submit', () => {
|
|
19
|
+
this.shouldFocusFirstInvalidField = true;
|
|
20
|
+
});
|
|
21
|
+
// onSubmit only fires once validation has passed, so disarm the focus
|
|
22
|
+
// flag: a successful submit must not leave it set, or the next unrelated
|
|
23
|
+
// async field error (e.g. NeverBounce on blur) would steal focus.
|
|
24
|
+
EnForm.getInstance().onSubmit.subscribe(() => {
|
|
25
|
+
this.shouldFocusFirstInvalidField = false;
|
|
26
|
+
});
|
|
14
27
|
}
|
|
15
28
|
/**
|
|
16
|
-
* Apply the field-level accessibility tagging (error
|
|
29
|
+
* Apply the field-level accessibility tagging (error containers,
|
|
17
30
|
* aria-required, fallback aria-labels, radio group roles) to every field
|
|
18
31
|
* within `root`. Defaults to the whole document on initial load, but can be
|
|
19
32
|
* pointed at a freshly injected fragment (e.g. a Supporter Hub overlay) so
|
|
@@ -21,24 +34,29 @@ export class A11y {
|
|
|
21
34
|
* idempotent, so re-scanning already tagged fields is safe.
|
|
22
35
|
*/
|
|
23
36
|
static scanFields(root = document) {
|
|
24
|
-
A11y.
|
|
37
|
+
A11y.addGlobalErrorLiveRegion(root);
|
|
25
38
|
A11y.addRequired(root);
|
|
26
39
|
A11y.addLabel(root);
|
|
27
40
|
A11y.addGroupRole(root);
|
|
28
41
|
}
|
|
29
|
-
static
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
static addGlobalErrorLiveRegion(root = document) {
|
|
43
|
+
var _a;
|
|
44
|
+
const liveRegionId = 'engrid-a11y-error-summary';
|
|
45
|
+
if (document.getElementById(liveRegionId))
|
|
46
|
+
return;
|
|
47
|
+
const form = (_a = ENGrid.enForm) !== null && _a !== void 0 ? _a : root.querySelector('form.en__component');
|
|
48
|
+
if (!form)
|
|
49
|
+
return;
|
|
50
|
+
const region = document.createElement('div');
|
|
51
|
+
region.id = liveRegionId;
|
|
52
|
+
region.className = 'engrid__sr-only';
|
|
53
|
+
region.setAttribute('aria-live', 'polite');
|
|
54
|
+
region.setAttribute('aria-atomic', 'true');
|
|
55
|
+
form.insertBefore(region, form.firstChild);
|
|
56
|
+
const errorList = document.querySelector('ul.en__errorList');
|
|
57
|
+
if (errorList === null || errorList === void 0 ? void 0 : errorList.hasAttribute('role')) {
|
|
58
|
+
errorList.removeAttribute('role');
|
|
59
|
+
}
|
|
42
60
|
}
|
|
43
61
|
static addGroupRole(root = document) {
|
|
44
62
|
// Add role="group" to all EN Radio fields
|
|
@@ -52,6 +70,13 @@ export class A11y {
|
|
|
52
70
|
const label = field.querySelector("label");
|
|
53
71
|
if (label) {
|
|
54
72
|
label.setAttribute("id", `en__field__label--${Math.random().toString(36).slice(2, 7)}`);
|
|
73
|
+
// EN renders the group's question label as a <label>, but a radio group
|
|
74
|
+
// has no single control for `for` to point at. When EN leaves it blank
|
|
75
|
+
// (`for=""`) it's an invalid IDREF, so strip only that empty case
|
|
76
|
+
const forAttr = label.getAttribute("for");
|
|
77
|
+
if (forAttr !== null && forAttr.trim() === "") {
|
|
78
|
+
label.removeAttribute("for");
|
|
79
|
+
}
|
|
55
80
|
field.setAttribute("aria-labelledby", label.id);
|
|
56
81
|
}
|
|
57
82
|
});
|
|
@@ -128,11 +153,12 @@ export class A11y {
|
|
|
128
153
|
});
|
|
129
154
|
}
|
|
130
155
|
/**
|
|
131
|
-
* Observe #engrid for .en__field__error additions and removals,
|
|
132
|
-
*
|
|
133
|
-
* aria-invalid / aria-describedby on the corresponding input
|
|
134
|
-
*
|
|
135
|
-
*
|
|
156
|
+
* Observe #engrid for .en__field__error additions and removals, relocating
|
|
157
|
+
* each error after its field's input so DOM order matches visual order. Also
|
|
158
|
+
* toggles aria-invalid / aria-describedby on the corresponding input (or radio
|
|
159
|
+
* group when the field has role="group") and updates a single, DOM-ordered
|
|
160
|
+
* global live region. Runs for the lifetime of the page so async validators
|
|
161
|
+
* (NeverBounce, VGS, server re-renders) are caught without timing assumptions.
|
|
136
162
|
*/
|
|
137
163
|
observeErrorMessages() {
|
|
138
164
|
var _a;
|
|
@@ -143,22 +169,22 @@ export class A11y {
|
|
|
143
169
|
continue;
|
|
144
170
|
record.addedNodes.forEach(node => {
|
|
145
171
|
if (node instanceof HTMLElement && node.classList.contains('en__field__error')) {
|
|
146
|
-
this.
|
|
172
|
+
this.tagFieldError(node);
|
|
147
173
|
}
|
|
148
174
|
});
|
|
149
175
|
record.removedNodes.forEach(node => {
|
|
150
|
-
var _a, _b;
|
|
151
176
|
if (!(node instanceof HTMLElement))
|
|
152
177
|
return;
|
|
153
178
|
if (!node.classList.contains('en__field__error'))
|
|
154
179
|
return;
|
|
180
|
+
if (node.isConnected)
|
|
181
|
+
return; // relocated, not removed
|
|
155
182
|
// node.parentElement is null after removal; record.target is the
|
|
156
183
|
// former parent. Walk up to the enclosing .en__field to be defensive
|
|
157
184
|
// against deeper nesting.
|
|
158
|
-
const fieldWrapper =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.clearErrorMessage(alert);
|
|
185
|
+
const fieldWrapper = record.target.closest('.en__field');
|
|
186
|
+
if (fieldWrapper)
|
|
187
|
+
this.clearFieldError(fieldWrapper, node.id);
|
|
162
188
|
});
|
|
163
189
|
}
|
|
164
190
|
});
|
|
@@ -167,55 +193,76 @@ export class A11y {
|
|
|
167
193
|
// before this observer was attached.
|
|
168
194
|
document.querySelectorAll('.en__field').forEach(field => {
|
|
169
195
|
const error = field.querySelector('.en__field__error');
|
|
170
|
-
const alert = field.querySelector('.en__field__error__alert');
|
|
171
196
|
if (error)
|
|
172
|
-
this.
|
|
173
|
-
else if (alert)
|
|
174
|
-
this.clearErrorMessage(alert);
|
|
197
|
+
this.tagFieldError(error);
|
|
175
198
|
});
|
|
176
199
|
}
|
|
177
|
-
|
|
200
|
+
tagFieldError(error) {
|
|
178
201
|
var _a;
|
|
179
|
-
|
|
180
|
-
return;
|
|
181
|
-
const fieldWrapper = field.closest('.en__field');
|
|
202
|
+
const fieldWrapper = error.closest('.en__field');
|
|
182
203
|
if (!fieldWrapper)
|
|
183
204
|
return;
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
const fieldElement = fieldWrapper.querySelector('.en__field__element');
|
|
206
|
+
if (fieldElement && fieldElement.nextElementSibling !== error) {
|
|
207
|
+
fieldElement.insertAdjacentElement('afterend', error);
|
|
208
|
+
}
|
|
209
|
+
if (!error.id) {
|
|
210
|
+
error.id = `en__field__error--a11y-${Math.random().toString(36).slice(2, 7)}`;
|
|
211
|
+
}
|
|
212
|
+
this.scheduleLiveRegionUpdate();
|
|
213
|
+
const target = fieldWrapper.getAttribute('role') === 'group'
|
|
214
|
+
? fieldWrapper
|
|
215
|
+
: fieldWrapper.querySelector('.en__field__element input, .en__field__element select, .en__field__element textarea');
|
|
216
|
+
if (!target)
|
|
191
217
|
return;
|
|
192
|
-
|
|
193
|
-
const describedBy = ((_a =
|
|
218
|
+
target.setAttribute('aria-invalid', 'true');
|
|
219
|
+
const describedBy = ((_a = target.getAttribute('aria-describedby')) !== null && _a !== void 0 ? _a : '')
|
|
194
220
|
.split(/\s+/)
|
|
195
221
|
.filter(Boolean);
|
|
196
|
-
if (describedBy.indexOf(
|
|
197
|
-
describedBy.push(
|
|
222
|
+
if (describedBy.indexOf(error.id) === -1) {
|
|
223
|
+
describedBy.push(error.id);
|
|
224
|
+
}
|
|
225
|
+
target.setAttribute('aria-describedby', describedBy.join(' '));
|
|
226
|
+
// For radio groups, the group itself is the accessible widget; keep the
|
|
227
|
+
// individual inputs from also being announced as invalid.
|
|
228
|
+
if (target === fieldWrapper) {
|
|
229
|
+
fieldWrapper
|
|
230
|
+
.querySelectorAll('.en__field__element input, .en__field__element select, .en__field__element textarea')
|
|
231
|
+
.forEach(input => {
|
|
232
|
+
var _a;
|
|
233
|
+
input.removeAttribute('aria-invalid');
|
|
234
|
+
const remaining = ((_a = input.getAttribute('aria-describedby')) !== null && _a !== void 0 ? _a : '')
|
|
235
|
+
.split(/\s+/)
|
|
236
|
+
.filter(id => id && id !== error.id);
|
|
237
|
+
if (remaining.length) {
|
|
238
|
+
input.setAttribute('aria-describedby', remaining.join(' '));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
input.removeAttribute('aria-describedby');
|
|
242
|
+
}
|
|
243
|
+
});
|
|
198
244
|
}
|
|
199
|
-
inputElement.setAttribute('aria-describedby', describedBy.join(' '));
|
|
200
245
|
}
|
|
201
|
-
|
|
246
|
+
clearFieldError(fieldWrapper, errorId) {
|
|
202
247
|
var _a;
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (!inputElement)
|
|
248
|
+
this.scheduleLiveRegionUpdate();
|
|
249
|
+
const target = fieldWrapper.getAttribute('role') === 'group'
|
|
250
|
+
? fieldWrapper
|
|
251
|
+
: fieldWrapper.querySelector('.en__field__element input, .en__field__element select, .en__field__element textarea');
|
|
252
|
+
if (!target)
|
|
209
253
|
return;
|
|
210
|
-
|
|
211
|
-
|
|
254
|
+
const hasRemainingError = fieldWrapper.querySelector('.en__field__error') !== null;
|
|
255
|
+
if (!hasRemainingError) {
|
|
256
|
+
target.removeAttribute('aria-invalid');
|
|
257
|
+
}
|
|
258
|
+
const remaining = ((_a = target.getAttribute('aria-describedby')) !== null && _a !== void 0 ? _a : '')
|
|
212
259
|
.split(/\s+/)
|
|
213
|
-
.filter(id => id && id !==
|
|
260
|
+
.filter(id => id && id !== errorId);
|
|
214
261
|
if (remaining.length) {
|
|
215
|
-
|
|
262
|
+
target.setAttribute('aria-describedby', remaining.join(' '));
|
|
216
263
|
}
|
|
217
264
|
else {
|
|
218
|
-
|
|
265
|
+
target.removeAttribute('aria-describedby');
|
|
219
266
|
}
|
|
220
267
|
}
|
|
221
268
|
/**
|
|
@@ -259,29 +306,108 @@ export class A11y {
|
|
|
259
306
|
});
|
|
260
307
|
}
|
|
261
308
|
}
|
|
262
|
-
|
|
309
|
+
scheduleLiveRegionUpdate() {
|
|
310
|
+
if (this.liveRegionUpdateTimeout !== null) {
|
|
311
|
+
window.clearTimeout(this.liveRegionUpdateTimeout);
|
|
312
|
+
}
|
|
313
|
+
this.liveRegionUpdateTimeout = window.setTimeout(() => {
|
|
314
|
+
this.liveRegionUpdateTimeout = null;
|
|
315
|
+
this.updateGlobalErrorLiveRegion();
|
|
316
|
+
}, 100);
|
|
317
|
+
}
|
|
318
|
+
updateGlobalErrorLiveRegion() {
|
|
319
|
+
var _a;
|
|
320
|
+
const region = document.getElementById('engrid-a11y-error-summary');
|
|
321
|
+
if (!region)
|
|
322
|
+
return;
|
|
263
323
|
const errorList = document.querySelector('ul.en__errorList');
|
|
264
|
-
if (
|
|
324
|
+
if (errorList === null || errorList === void 0 ? void 0 : errorList.hasAttribute('role')) {
|
|
325
|
+
errorList.removeAttribute('role');
|
|
326
|
+
}
|
|
327
|
+
const fields = Array.from(document.querySelectorAll('.en__field'));
|
|
328
|
+
const messages = fields
|
|
329
|
+
.map(field => {
|
|
330
|
+
var _a;
|
|
331
|
+
const error = field.querySelector('.en__field__error');
|
|
332
|
+
if (!((_a = error === null || error === void 0 ? void 0 : error.textContent) === null || _a === void 0 ? void 0 : _a.trim()))
|
|
333
|
+
return null;
|
|
334
|
+
const label = this.getFieldLabel(field);
|
|
335
|
+
const message = this.formatErrorMessage(label, error.textContent.trim());
|
|
336
|
+
return message;
|
|
337
|
+
})
|
|
338
|
+
.filter((message) => Boolean(message));
|
|
339
|
+
// Top-of-form server errors (ul.en__errorList) aren't tied to a .en__field,
|
|
340
|
+
// so fold in any that aren't already covered by the per-field messages,
|
|
341
|
+
// otherwise they would be silent for screen readers.
|
|
342
|
+
const normalizeForCompare = (value) => value.replace(/\s+/g, ' ').replace(/[.!?:]+$/, '').trim().toLowerCase();
|
|
343
|
+
const coveredMessages = messages.map(normalizeForCompare).filter(Boolean);
|
|
344
|
+
const serverMessages = Array.from((_a = errorList === null || errorList === void 0 ? void 0 : errorList.querySelectorAll('li')) !== null && _a !== void 0 ? _a : [])
|
|
345
|
+
.map(item => { var _a, _b; return (_b = (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : ''; })
|
|
346
|
+
.filter(text => {
|
|
347
|
+
if (!text)
|
|
348
|
+
return false;
|
|
349
|
+
const normalized = normalizeForCompare(text);
|
|
350
|
+
if (!normalized)
|
|
351
|
+
return false;
|
|
352
|
+
return !coveredMessages.some(covered => covered === normalized ||
|
|
353
|
+
covered.includes(normalized) ||
|
|
354
|
+
normalized.includes(covered));
|
|
355
|
+
});
|
|
356
|
+
const allMessages = [...messages, ...serverMessages];
|
|
357
|
+
region.textContent = '';
|
|
358
|
+
if (!allMessages.length) {
|
|
359
|
+
this.shouldFocusFirstInvalidField = false;
|
|
265
360
|
return;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
361
|
+
}
|
|
362
|
+
if (allMessages.length === 1) {
|
|
363
|
+
region.textContent = allMessages[0];
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const cleaned = allMessages.map(message => message.replace(/[.!?]+$/, '').trim());
|
|
367
|
+
region.textContent = `There are ${allMessages.length} errors: ${cleaned.join('. ')}.`;
|
|
368
|
+
}
|
|
369
|
+
if (this.shouldFocusFirstInvalidField) {
|
|
370
|
+
this.shouldFocusFirstInvalidField = false;
|
|
371
|
+
this.focusFirstInvalidField();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
formatErrorMessage(label, message) {
|
|
375
|
+
const cleanMessage = message.trim();
|
|
376
|
+
const cleanLabel = this.normalizeLabel(label);
|
|
377
|
+
if (!cleanLabel)
|
|
378
|
+
return cleanMessage;
|
|
379
|
+
// If the message already mentions the field label, no need to prefix it.
|
|
380
|
+
const escapedLabel = cleanLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
381
|
+
const labelRegex = new RegExp(`\\b${escapedLabel}\\b`, 'i');
|
|
382
|
+
if (labelRegex.test(cleanMessage))
|
|
383
|
+
return cleanMessage;
|
|
384
|
+
return `${cleanLabel}: ${cleanMessage}`;
|
|
385
|
+
}
|
|
386
|
+
focusFirstInvalidField() {
|
|
387
|
+
const fields = Array.from(document.querySelectorAll('.en__field'));
|
|
388
|
+
for (const field of fields) {
|
|
389
|
+
if (!field.querySelector('.en__field__error'))
|
|
390
|
+
continue;
|
|
391
|
+
const target = field.querySelector('.en__field__element input:not([type="hidden"]), .en__field__element select, .en__field__element textarea');
|
|
392
|
+
if (target && target.offsetParent !== null && !target.hasAttribute('disabled')) {
|
|
393
|
+
target.focus();
|
|
394
|
+
return;
|
|
284
395
|
}
|
|
285
|
-
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
getFieldLabel(field) {
|
|
399
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
400
|
+
const label = (_c = (_b = (_a = field.querySelector('.en__field__label:not(.en__field__label--item)')) !== null && _a !== void 0 ? _a : field.querySelector('.en__field__label')) !== null && _b !== void 0 ? _b : field.querySelector('label')) !== null && _c !== void 0 ? _c : field.querySelector('legend');
|
|
401
|
+
const labelText = (_e = (_d = label === null || label === void 0 ? void 0 : label.textContent) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e : '';
|
|
402
|
+
if (labelText)
|
|
403
|
+
return this.normalizeLabel(labelText);
|
|
404
|
+
const input = field.querySelector('input, select, textarea');
|
|
405
|
+
return (_g = (_f = input === null || input === void 0 ? void 0 : input.getAttribute('aria-label')) === null || _f === void 0 ? void 0 : _f.trim()) !== null && _g !== void 0 ? _g : '';
|
|
406
|
+
}
|
|
407
|
+
normalizeLabel(label) {
|
|
408
|
+
return label
|
|
409
|
+
.replace(/\s+/g, ' ')
|
|
410
|
+
.replace(/^[*:\s]+|[*:\s]+$/g, '')
|
|
411
|
+
.trim();
|
|
286
412
|
}
|
|
287
413
|
}
|
|
@@ -3,6 +3,8 @@ export declare class DonationFrequency {
|
|
|
3
3
|
private _frequency;
|
|
4
4
|
private _recurring;
|
|
5
5
|
private _dispatch;
|
|
6
|
+
private _frequencies;
|
|
7
|
+
private logger;
|
|
6
8
|
private static instance;
|
|
7
9
|
private constructor();
|
|
8
10
|
static getInstance(): DonationFrequency;
|
|
@@ -11,6 +13,7 @@ export declare class DonationFrequency {
|
|
|
11
13
|
get recurring(): string;
|
|
12
14
|
set recurring(value: string);
|
|
13
15
|
get onFrequencyChange(): import("strongly-typed-events").ISimpleEvent<string>;
|
|
16
|
+
get frequencies(): string[];
|
|
14
17
|
load(): void;
|
|
15
18
|
setRecurrency(recurr: string, dispatch?: boolean): void;
|
|
16
19
|
setFrequency(freq: string, dispatch?: boolean): void;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { SimpleEventDispatcher } from "strongly-typed-events";
|
|
2
2
|
import { ENGrid } from "../engrid";
|
|
3
|
+
import { EngridLogger } from "../logger";
|
|
3
4
|
export class DonationFrequency {
|
|
4
5
|
constructor() {
|
|
5
6
|
this._onFrequencyChange = new SimpleEventDispatcher();
|
|
6
7
|
this._frequency = "onetime";
|
|
7
8
|
this._recurring = "n";
|
|
8
9
|
this._dispatch = true;
|
|
10
|
+
this._frequencies = ["onetime"];
|
|
11
|
+
this.logger = new EngridLogger("DonationFrequency", "white", "black", "💰");
|
|
9
12
|
// Watch the Radios for Changes
|
|
10
13
|
document.addEventListener("change", (e) => {
|
|
11
14
|
const element = e.target;
|
|
@@ -57,6 +60,9 @@ export class DonationFrequency {
|
|
|
57
60
|
get onFrequencyChange() {
|
|
58
61
|
return this._onFrequencyChange.asEvent();
|
|
59
62
|
}
|
|
63
|
+
get frequencies() {
|
|
64
|
+
return this._frequencies;
|
|
65
|
+
}
|
|
60
66
|
// Set amount var with currently selected amount
|
|
61
67
|
load() {
|
|
62
68
|
var _a;
|
|
@@ -73,6 +79,11 @@ export class DonationFrequency {
|
|
|
73
79
|
((_a = window.EngagingNetworks.require._defined.enjs
|
|
74
80
|
.getSupporterData("recurrpay")) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || "n";
|
|
75
81
|
}
|
|
82
|
+
// List of available frequencies on the form
|
|
83
|
+
this._frequencies = Array.from(document.querySelectorAll('input[name="transaction.recurrfreq"]'))
|
|
84
|
+
.filter((el) => el instanceof HTMLInputElement)
|
|
85
|
+
.map((el) => el.value.toLowerCase());
|
|
86
|
+
this.logger.log(`Loaded with frequency: ${this.frequency} and recurring: ${this.recurring} \nAvailable frequencies: ${this._frequencies.join(", ")}`);
|
|
76
87
|
// ENGrid.enParseDependencies();
|
|
77
88
|
}
|
|
78
89
|
// Force a new recurrency
|
|
@@ -109,6 +120,9 @@ export class DonationFrequency {
|
|
|
109
120
|
this.setRecurrency("Y", dispatch);
|
|
110
121
|
}
|
|
111
122
|
}
|
|
123
|
+
else {
|
|
124
|
+
this.logger.warn(`Attempted to set a frequency of "${freq}" but it was not found on the form.`);
|
|
125
|
+
}
|
|
112
126
|
// Revert dispatch to default value (true)
|
|
113
127
|
this._dispatch = true;
|
|
114
128
|
}
|
package/dist/frequency-upsell.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// ! WE ARE PHASING OUT THIS COMPONENT IN FAVOR OF UPSELL-LIGHTBOX. PLEASE USE THAT COMPONENT FOR NEW IMPLEMENTATIONS.
|
|
1
2
|
/*
|
|
2
3
|
* FrequencyUpsell component which creates a modal to upsell the frequency of the donation
|
|
3
4
|
* This is typically used to upsell a single donation into an annual donation, but the component
|
|
@@ -11,11 +11,14 @@ export interface UpsellOptions {
|
|
|
11
11
|
amountRange: Array<{
|
|
12
12
|
max: number;
|
|
13
13
|
suggestion: number | string;
|
|
14
|
+
frequency?: "onetime" | "monthly" | "quarterly" | "semi_annual" | "annual";
|
|
14
15
|
}>;
|
|
16
|
+
upsellToFrequency?: "onetime" | "monthly" | "quarterly" | "semi_annual" | "annual";
|
|
15
17
|
minAmount: number;
|
|
16
18
|
canClose: boolean;
|
|
17
19
|
submitOnClose: boolean;
|
|
18
20
|
oneTime: boolean;
|
|
21
|
+
monthly: boolean;
|
|
19
22
|
annual: boolean;
|
|
20
23
|
disablePaymentMethods: Array<string>;
|
|
21
24
|
skipUpsell: boolean;
|
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
export const UpsellOptionsDefaults = {
|
|
2
2
|
image: "https://picsum.photos/480/650",
|
|
3
3
|
imagePosition: "left",
|
|
4
|
-
title: "Will you change your gift to just {new-amount}
|
|
5
|
-
paragraph: "Make a
|
|
6
|
-
yesLabel: "Yes! Process My <br> {new-amount}
|
|
7
|
-
noLabel: "No, thanks. Continue with my <br> {old-amount}
|
|
4
|
+
title: "Will you change your gift to just {new-amount} {new-frequency} to boost your impact?",
|
|
5
|
+
paragraph: "Make a {new-frequency} pledge today to support us with consistent, reliable resources during emergency moments.",
|
|
6
|
+
yesLabel: "Yes! Process My <br> {new-amount} {new-frequency} gift",
|
|
7
|
+
noLabel: "No, thanks. Continue with my <br> {old-amount} {old-frequency} gift",
|
|
8
8
|
otherAmount: true,
|
|
9
|
-
otherLabel: "Or enter a different
|
|
9
|
+
otherLabel: "Or enter a different {new-frequency} amount:",
|
|
10
10
|
upsellOriginalGiftAmountFieldName: "",
|
|
11
11
|
amountRange: [
|
|
12
|
-
{ max: 10, suggestion: 5 },
|
|
13
|
-
{ max: 15, suggestion: 7 },
|
|
14
|
-
{ max: 20, suggestion: 8 },
|
|
15
|
-
{ max: 25, suggestion: 9 },
|
|
16
|
-
{ max: 30, suggestion: 10 },
|
|
17
|
-
{ max: 35, suggestion: 11 },
|
|
18
|
-
{ max: 40, suggestion: 12 },
|
|
19
|
-
{ max: 50, suggestion: 14 },
|
|
20
|
-
{ max: 100, suggestion: 15 },
|
|
21
|
-
{ max: 200, suggestion: 19 },
|
|
22
|
-
{ max: 300, suggestion: 29 },
|
|
23
|
-
{
|
|
12
|
+
{ max: 10, suggestion: 5, frequency: "monthly" },
|
|
13
|
+
{ max: 15, suggestion: 7, frequency: "monthly" },
|
|
14
|
+
{ max: 20, suggestion: 8, frequency: "monthly" },
|
|
15
|
+
{ max: 25, suggestion: 9, frequency: "monthly" },
|
|
16
|
+
{ max: 30, suggestion: 10, frequency: "monthly" },
|
|
17
|
+
{ max: 35, suggestion: 11, frequency: "monthly" },
|
|
18
|
+
{ max: 40, suggestion: 12, frequency: "monthly" },
|
|
19
|
+
{ max: 50, suggestion: 14, frequency: "monthly" },
|
|
20
|
+
{ max: 100, suggestion: 15, frequency: "monthly" },
|
|
21
|
+
{ max: 200, suggestion: 19, frequency: "monthly" },
|
|
22
|
+
{ max: 300, suggestion: 29, frequency: "monthly" },
|
|
23
|
+
{
|
|
24
|
+
max: 500,
|
|
25
|
+
suggestion: "Math.ceil((amount / 12)/5)*5",
|
|
26
|
+
frequency: "monthly",
|
|
27
|
+
},
|
|
24
28
|
],
|
|
29
|
+
upsellToFrequency: "monthly",
|
|
25
30
|
minAmount: 0,
|
|
26
31
|
canClose: true,
|
|
27
32
|
submitOnClose: false,
|
|
28
33
|
oneTime: true,
|
|
34
|
+
monthly: false,
|
|
29
35
|
annual: false,
|
|
30
36
|
disablePaymentMethods: [],
|
|
31
37
|
skipUpsell: false,
|
package/dist/page-background.js
CHANGED
|
@@ -7,6 +7,9 @@ export class PageBackground {
|
|
|
7
7
|
this.bodyBannerImage = null;
|
|
8
8
|
this.mutationObserver = null;
|
|
9
9
|
this.logger = new EngridLogger("PageBackground", "lightblue", "darkblue", "🖼️");
|
|
10
|
+
if (typeof window.UseBodyBannerImageAsBackground !== "undefined") {
|
|
11
|
+
useBodyBannerImage = !!window.UseBodyBannerImageAsBackground;
|
|
12
|
+
}
|
|
10
13
|
if (useBodyBannerImage) {
|
|
11
14
|
this.bodyBannerImage = this.findBodyBannerImage();
|
|
12
15
|
}
|
|
@@ -9,15 +9,17 @@ export declare class UpsellLightbox {
|
|
|
9
9
|
private _frequency;
|
|
10
10
|
private _dataLayer;
|
|
11
11
|
private _suggestAmount;
|
|
12
|
+
private _upsellFrequency;
|
|
12
13
|
private logger;
|
|
13
14
|
constructor();
|
|
15
|
+
private parseMergeTags;
|
|
14
16
|
private renderLightbox;
|
|
15
17
|
private shouldRun;
|
|
16
18
|
private shouldSkip;
|
|
17
19
|
private popupOtherField;
|
|
18
20
|
private liveAmounts;
|
|
19
21
|
private liveFrequency;
|
|
20
|
-
private
|
|
22
|
+
private resolveUpsell;
|
|
21
23
|
private shouldOpen;
|
|
22
24
|
private freqAllowed;
|
|
23
25
|
private open;
|
package/dist/upsell-lightbox.js
CHANGED
|
@@ -9,6 +9,7 @@ export class UpsellLightbox {
|
|
|
9
9
|
this._frequency = DonationFrequency.getInstance();
|
|
10
10
|
this._dataLayer = DataLayer.getInstance();
|
|
11
11
|
this._suggestAmount = 0;
|
|
12
|
+
this._upsellFrequency = "monthly";
|
|
12
13
|
this.logger = new EngridLogger("UpsellLightbox", "black", "pink", "🪟");
|
|
13
14
|
let options = "EngridUpsell" in window ? window.EngridUpsell : {};
|
|
14
15
|
this.options = Object.assign(Object.assign({}, UpsellOptionsDefaults), options);
|
|
@@ -26,23 +27,19 @@ export class UpsellLightbox {
|
|
|
26
27
|
this.renderLightbox();
|
|
27
28
|
this._form.onSubmit.subscribe(() => this.open());
|
|
28
29
|
}
|
|
30
|
+
parseMergeTags(str) {
|
|
31
|
+
return str
|
|
32
|
+
.replace(/\{new-amount\}/g, "<span class='upsell_suggestion'></span>")
|
|
33
|
+
.replace(/\{new-frequency\}/g, "<span class='upsell_suggestion_frequency'></span>")
|
|
34
|
+
.replace(/\{old-amount\}/g, "<span class='upsell_amount'></span>")
|
|
35
|
+
.replace(/\{old-frequency\}/g, "<span class='upsell_frequency'></span>");
|
|
36
|
+
}
|
|
29
37
|
renderLightbox() {
|
|
30
|
-
const title = this.options.title
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
.replace("{new-amount}", "<span class='upsell_suggestion'></span>")
|
|
36
|
-
.replace("{old-amount}", "<span class='upsell_amount'></span>")
|
|
37
|
-
.replace("{old-frequency}", "<span class='upsell_frequency'></span>");
|
|
38
|
-
const yes = this.options.yesLabel
|
|
39
|
-
.replace("{new-amount}", "<span class='upsell_suggestion'></span>")
|
|
40
|
-
.replace("{old-amount}", "<span class='upsell_amount'></span>")
|
|
41
|
-
.replace("{old-frequency}", "<span class='upsell_frequency'></span>");
|
|
42
|
-
const no = this.options.noLabel
|
|
43
|
-
.replace("{new-amount}", "<span class='upsell_suggestion'></span>")
|
|
44
|
-
.replace("{old-amount}", "<span class='upsell_amount'></span>")
|
|
45
|
-
.replace("{old-frequency}", "<span class='upsell_frequency'></span>");
|
|
38
|
+
const title = this.parseMergeTags(this.options.title);
|
|
39
|
+
const paragraph = this.parseMergeTags(this.options.paragraph);
|
|
40
|
+
const yes = this.parseMergeTags(this.options.yesLabel);
|
|
41
|
+
const no = this.parseMergeTags(this.options.noLabel);
|
|
42
|
+
const other = this.parseMergeTags(this.options.otherLabel);
|
|
46
43
|
const markup = `
|
|
47
44
|
<div class="upsellLightboxContainer" id="goMonthly">
|
|
48
45
|
<!-- ideal image size is 480x650 pixels -->
|
|
@@ -57,7 +54,7 @@ export class UpsellLightbox {
|
|
|
57
54
|
<div class="upsellOtherAmount">
|
|
58
55
|
<div class="upsellOtherAmountLabel">
|
|
59
56
|
<p>
|
|
60
|
-
${
|
|
57
|
+
${other}
|
|
61
58
|
</p>
|
|
62
59
|
</div>
|
|
63
60
|
<div class="upsellOtherAmountInput">
|
|
@@ -140,7 +137,7 @@ export class UpsellLightbox {
|
|
|
140
137
|
var _a, _b;
|
|
141
138
|
const value = parseFloat((_b = (_a = this.overlay.querySelector("#secondOtherField")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "");
|
|
142
139
|
const live_upsell_amount = document.querySelectorAll("#upsellYesButton .upsell_suggestion");
|
|
143
|
-
const upsellAmount = this.
|
|
140
|
+
const { amount: upsellAmount } = this.resolveUpsell();
|
|
144
141
|
if (!isNaN(value) && value > 0) {
|
|
145
142
|
this.checkOtherAmount(value);
|
|
146
143
|
}
|
|
@@ -152,59 +149,87 @@ export class UpsellLightbox {
|
|
|
152
149
|
liveAmounts() {
|
|
153
150
|
const live_upsell_amount = document.querySelectorAll(".upsell_suggestion");
|
|
154
151
|
const live_amount = document.querySelectorAll(".upsell_amount");
|
|
155
|
-
const upsellAmount = this.
|
|
152
|
+
const { amount: upsellAmount } = this.resolveUpsell();
|
|
156
153
|
const suggestedAmount = upsellAmount + this._fees.calculateFees(upsellAmount);
|
|
157
154
|
live_upsell_amount.forEach((elem) => (elem.innerHTML = this.getAmountTxt(suggestedAmount)));
|
|
158
155
|
live_amount.forEach((elem) => (elem.innerHTML = this.getAmountTxt(this._amount.amount + this._fees.fee)));
|
|
159
156
|
}
|
|
160
157
|
liveFrequency() {
|
|
161
158
|
const live_upsell_frequency = document.querySelectorAll(".upsell_frequency");
|
|
159
|
+
const live_upsell_suggestion_frequency = document.querySelectorAll(".upsell_suggestion_frequency");
|
|
162
160
|
live_upsell_frequency.forEach((elem) => (elem.innerHTML = this.getFrequencyTxt()));
|
|
161
|
+
live_upsell_suggestion_frequency.forEach((elem) => (elem.innerHTML = this.getFrequencyTxt(this._upsellFrequency)));
|
|
163
162
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
// Resolve the upsell amount and target frequency in a single pass and keep
|
|
164
|
+
// the cached _suggestAmount / _upsellFrequency in sync with the current
|
|
165
|
+
// donation amount and any value entered in the "other amount" field.
|
|
166
|
+
resolveUpsell() {
|
|
167
|
+
var _a, _b, _c, _d, _e;
|
|
167
168
|
const amount = this._amount.amount;
|
|
168
169
|
const otherAmount = parseFloat((_b = (_a = this.overlay.querySelector("#secondOtherField")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "");
|
|
170
|
+
const defaultFrequency = (_c = this.options.upsellToFrequency) !== null && _c !== void 0 ? _c : "monthly";
|
|
171
|
+
let upsellAmount;
|
|
172
|
+
let upsellFrequency;
|
|
169
173
|
if (otherAmount > 0) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
// An "other" amount overrides the amount but keeps the frequency that
|
|
175
|
+
// was already shown when the lightbox opened
|
|
176
|
+
upsellAmount =
|
|
177
|
+
otherAmount > this.options.minAmount
|
|
178
|
+
? otherAmount
|
|
179
|
+
: this.options.minAmount;
|
|
180
|
+
upsellFrequency = this._upsellFrequency;
|
|
173
181
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (upsellAmount
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
else {
|
|
183
|
+
upsellAmount = 0;
|
|
184
|
+
upsellFrequency = defaultFrequency;
|
|
185
|
+
for (let i = 0; i < this.options.amountRange.length; i++) {
|
|
186
|
+
const val = this.options.amountRange[i];
|
|
187
|
+
if (upsellAmount == 0 && amount <= val.max) {
|
|
188
|
+
if (val.suggestion === 0) {
|
|
189
|
+
upsellFrequency = (_d = val.frequency) !== null && _d !== void 0 ? _d : defaultFrequency;
|
|
190
|
+
this._suggestAmount = 0;
|
|
191
|
+
this._upsellFrequency = upsellFrequency;
|
|
192
|
+
return { amount: 0, frequency: upsellFrequency };
|
|
193
|
+
}
|
|
194
|
+
else if (typeof val.suggestion === "number") {
|
|
195
|
+
upsellAmount = val.suggestion;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const suggestionMath = val.suggestion.replace("amount", amount.toFixed(2));
|
|
199
|
+
upsellAmount = parseFloat(Function('"use strict";return (' + suggestionMath + ")")());
|
|
200
|
+
}
|
|
201
|
+
upsellFrequency = (_e = val.frequency) !== null && _e !== void 0 ? _e : defaultFrequency;
|
|
202
|
+
break;
|
|
184
203
|
}
|
|
185
|
-
break;
|
|
186
204
|
}
|
|
205
|
+
upsellAmount =
|
|
206
|
+
upsellAmount > this.options.minAmount
|
|
207
|
+
? upsellAmount
|
|
208
|
+
: this.options.minAmount;
|
|
187
209
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
this._suggestAmount = upsellAmount;
|
|
211
|
+
this._upsellFrequency = upsellFrequency;
|
|
212
|
+
return { amount: upsellAmount, frequency: upsellFrequency };
|
|
191
213
|
}
|
|
192
214
|
shouldOpen() {
|
|
193
|
-
const upsellAmount = this.
|
|
215
|
+
const { amount: upsellAmount, frequency: upsellFrequency } = this.resolveUpsell();
|
|
194
216
|
const paymenttype = ENGrid.getFieldValue("transaction.paymenttype") || "";
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
217
|
+
// If frequency is not allowed, or
|
|
218
|
+
// the modal is already opened, or
|
|
219
|
+
// there's no suggestion for this donation amount, or
|
|
220
|
+
// the target upsell frequency is not available on the form,
|
|
199
221
|
// we should not open
|
|
200
222
|
if (this.freqAllowed() &&
|
|
201
223
|
!this.shouldSkip() &&
|
|
202
224
|
!this.options.disablePaymentMethods.includes(paymenttype.toLowerCase()) &&
|
|
203
225
|
!this.overlay.classList.contains("is-submitting") &&
|
|
204
|
-
upsellAmount > 0
|
|
226
|
+
upsellAmount > 0 &&
|
|
227
|
+
this._frequency.frequencies.includes(upsellFrequency) &&
|
|
228
|
+
this._frequency.frequency !== upsellFrequency) {
|
|
205
229
|
this.logger.log("Upsell Frequency " + this._frequency.frequency);
|
|
206
230
|
this.logger.log("Upsell Amount " + this._amount.amount);
|
|
207
231
|
this.logger.log("Upsell Suggested Amount " + upsellAmount);
|
|
232
|
+
this.logger.log("Upsell Suggested Frequency " + upsellFrequency);
|
|
208
233
|
return true;
|
|
209
234
|
}
|
|
210
235
|
return false;
|
|
@@ -215,6 +240,8 @@ export class UpsellLightbox {
|
|
|
215
240
|
const allowed = [];
|
|
216
241
|
if (this.options.oneTime)
|
|
217
242
|
allowed.push("onetime");
|
|
243
|
+
if (this.options.monthly)
|
|
244
|
+
allowed.push("monthly");
|
|
218
245
|
if (this.options.annual)
|
|
219
246
|
allowed.push("annual");
|
|
220
247
|
return allowed.includes(freq);
|
|
@@ -274,27 +301,30 @@ export class UpsellLightbox {
|
|
|
274
301
|
((_a = document.querySelector("#upsellYesButton")) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
|
|
275
302
|
this.logger.success("Upsold");
|
|
276
303
|
this.setOriginalAmount(this._amount.amount.toString());
|
|
277
|
-
const upsoldAmount = this.
|
|
304
|
+
const { amount: upsoldAmount, frequency: upsellFrequency } = this.resolveUpsell();
|
|
278
305
|
const originalAmount = this._amount.amount;
|
|
279
|
-
this._frequency.
|
|
306
|
+
const originalFrequency = this._frequency.frequency;
|
|
307
|
+
this._frequency.setFrequency(upsellFrequency);
|
|
280
308
|
this._amount.setAmount(upsoldAmount);
|
|
281
309
|
this._dataLayer.addEndOfGiftProcessEvent("ENGRID_UPSELL", {
|
|
282
310
|
eventValue: true,
|
|
311
|
+
originalFrequency: originalFrequency,
|
|
283
312
|
originalAmount: originalAmount,
|
|
284
313
|
upsoldAmount: upsoldAmount,
|
|
285
|
-
frequency:
|
|
314
|
+
frequency: upsellFrequency,
|
|
286
315
|
});
|
|
287
316
|
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL", true);
|
|
288
317
|
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL_ORIGINAL_AMOUNT", originalAmount);
|
|
289
|
-
this._dataLayer.addEndOfGiftProcessVariable("
|
|
290
|
-
this.
|
|
318
|
+
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL_ORIGINAL_FREQUENCY", this.getFrequencyTxt(originalFrequency).toUpperCase());
|
|
319
|
+
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL_DONATION_FREQUENCY", this.getFrequencyTxt(upsellFrequency).toUpperCase());
|
|
320
|
+
this.renderConversionField("upsellSuccess", originalFrequency, originalAmount, upsellFrequency, this._suggestAmount, upsellFrequency, upsoldAmount);
|
|
291
321
|
}
|
|
292
322
|
else {
|
|
293
323
|
this.setOriginalAmount("");
|
|
294
324
|
window.sessionStorage.removeItem("original");
|
|
295
325
|
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL", false);
|
|
296
|
-
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL_DONATION_FREQUENCY",
|
|
297
|
-
this.renderConversionField("upsellFail", this._frequency.frequency, this._amount.amount,
|
|
326
|
+
this._dataLayer.addEndOfGiftProcessVariable("ENGRID_UPSELL_DONATION_FREQUENCY", this.getFrequencyTxt(this._frequency.frequency).toUpperCase());
|
|
327
|
+
this.renderConversionField("upsellFail", this._frequency.frequency, this._amount.amount, this._upsellFrequency, this._suggestAmount, this._frequency.frequency, this._amount.amount);
|
|
298
328
|
}
|
|
299
329
|
this._form.submitForm();
|
|
300
330
|
}
|
|
@@ -304,7 +334,7 @@ export class UpsellLightbox {
|
|
|
304
334
|
this.overlay.classList.add("is-hidden");
|
|
305
335
|
ENGrid.setBodyData("has-lightbox", false);
|
|
306
336
|
if (this.options.submitOnClose) {
|
|
307
|
-
this.renderConversionField("upsellFail", this._frequency.frequency, this._amount.amount,
|
|
337
|
+
this.renderConversionField("upsellFail", this._frequency.frequency, this._amount.amount, this._upsellFrequency, this._suggestAmount, this._frequency.frequency, this._amount.amount);
|
|
308
338
|
this._form.submitForm();
|
|
309
339
|
}
|
|
310
340
|
else {
|
|
@@ -320,14 +350,16 @@ export class UpsellLightbox {
|
|
|
320
350
|
const amountTxt = ENGrid.formatNumber(amount, dec_places, dec_separator, thousands_separator);
|
|
321
351
|
return amount > 0 ? symbol + amountTxt : "";
|
|
322
352
|
}
|
|
323
|
-
getFrequencyTxt() {
|
|
353
|
+
getFrequencyTxt(frequency = this._frequency.frequency) {
|
|
324
354
|
const freqTxt = {
|
|
325
355
|
onetime: "one-time",
|
|
326
356
|
monthly: "monthly",
|
|
357
|
+
quarterly: "quarterly",
|
|
358
|
+
semi_annual: "semi-annual",
|
|
327
359
|
annual: "annual",
|
|
328
360
|
};
|
|
329
|
-
const
|
|
330
|
-
return
|
|
361
|
+
const freq = frequency;
|
|
362
|
+
return freq in freqTxt ? freqTxt[freq] : frequency;
|
|
331
363
|
}
|
|
332
364
|
checkOtherAmount(value) {
|
|
333
365
|
const otherInput = document.querySelector(".upsellOtherAmountInput");
|
|
@@ -343,7 +375,7 @@ export class UpsellLightbox {
|
|
|
343
375
|
renderConversionField(event, // The event that triggered the conversion
|
|
344
376
|
freq, // The frequency of the donation (onetime, monthly, annual)
|
|
345
377
|
amt, // The original amount of the donation (before the upsell)
|
|
346
|
-
sugFreq, // The suggested frequency of the upsell
|
|
378
|
+
sugFreq, // The suggested frequency of the upsell
|
|
347
379
|
sugAmt, // The suggested amount of the upsell
|
|
348
380
|
subFreq, // The submitted frequency of the upsell (onetime, monthly, annual)
|
|
349
381
|
subAmt // The submitted amount of the upsell
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AppVersion = "0.27.
|
|
1
|
+
export declare const AppVersion = "0.27.3";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AppVersion = "0.27.
|
|
1
|
+
export const AppVersion = "0.27.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4site/engrid-scripts",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.3",
|
|
4
4
|
"description": "Scripts for the ENGRID Project",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"strongly-typed-events": "^2.0.9"
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/4site-interactive-studios/engrid#readme",
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "b117056008fd80443843cefd5178ad9942a59843",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"nodemon": "^2.0.20"
|
|
31
31
|
}
|