@4site/engrid-scripts 0.28.0 → 0.28.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.
@@ -1,7 +1,10 @@
1
1
  export declare class InputPlaceholders {
2
2
  private defaultPlaceholders;
3
+ private selectorToI18nKey;
4
+ private customSelectors;
3
5
  constructor();
4
6
  private shouldRun;
5
7
  private run;
8
+ private resolvePlaceholder;
6
9
  private addPlaceholder;
7
10
  }
@@ -12,6 +12,9 @@
12
12
  import { ENGrid } from ".";
13
13
  export class InputPlaceholders {
14
14
  constructor() {
15
+ // NOTE: for selectors listed in selectorToI18nKey below, these English
16
+ // strings are shadowed by the i18n dictionary — edit
17
+ // interfaces/i18n-options.ts ("placeholders.*" keys) instead of here.
15
18
  this.defaultPlaceholders = {
16
19
  "input#en__field_supporter_firstName": "First Name",
17
20
  "input#en__field_supporter_lastName": "Last Name",
@@ -57,10 +60,30 @@ export class InputPlaceholders {
57
60
  "input#en__field_supporter_billingRegion": "Billing Region",
58
61
  "input#en__field_supporter_billingPostcode": "Billing Postal Code",
59
62
  };
63
+ // Maps the default-placeholder selectors to i18n dictionary keys, so the
64
+ // built-in strings follow the page language. Selectors the client overrides
65
+ // via the Placeholders option are never translated.
66
+ this.selectorToI18nKey = {
67
+ "input#en__field_supporter_firstName": "placeholders.firstName",
68
+ "input#en__field_supporter_lastName": "placeholders.lastName",
69
+ "input#en__field_supporter_emailAddress": "placeholders.emailAddress",
70
+ "input#en__field_supporter_phoneNumber": "placeholders.phoneNumberOptional",
71
+ ".en__mandatory input#en__field_supporter_phoneNumber": "placeholders.phoneNumber",
72
+ ".i-required input#en__field_supporter_phoneNumber": "placeholders.phoneNumber",
73
+ "input#en__field_supporter_phoneNumber2": "placeholders.phoneNumber2Optional",
74
+ "input#en__field_supporter_country": "placeholders.country",
75
+ "input#en__field_supporter_address1": "placeholders.address1",
76
+ "input#en__field_supporter_address2": "placeholders.address2",
77
+ "input#en__field_supporter_city": "placeholders.city",
78
+ "input#en__field_supporter_region": "placeholders.region",
79
+ "input#en__field_supporter_postcode": "placeholders.postcode",
80
+ };
81
+ this.customSelectors = new Set();
60
82
  if (this.shouldRun()) {
61
83
  // If there's a Placeholders option, merge it with the default placeholders
62
84
  const placeholders = ENGrid.getOption("Placeholders");
63
85
  if (placeholders) {
86
+ this.customSelectors = new Set(Object.keys(placeholders));
64
87
  this.defaultPlaceholders = Object.assign(Object.assign({}, this.defaultPlaceholders), placeholders);
65
88
  }
66
89
  this.run();
@@ -72,9 +95,18 @@ export class InputPlaceholders {
72
95
  run() {
73
96
  Object.keys(this.defaultPlaceholders).forEach((selector) => {
74
97
  if (selector in this.defaultPlaceholders)
75
- this.addPlaceholder(selector, this.defaultPlaceholders[selector]);
98
+ this.addPlaceholder(selector, this.resolvePlaceholder(selector));
76
99
  });
77
100
  }
101
+ // Built-in placeholder strings follow the page language; client-provided
102
+ // Placeholders options always win.
103
+ resolvePlaceholder(selector) {
104
+ const key = this.selectorToI18nKey[selector];
105
+ if (key && !this.customSelectors.has(selector)) {
106
+ return ENGrid.t(key);
107
+ }
108
+ return this.defaultPlaceholders[selector];
109
+ }
78
110
  addPlaceholder(selector, placeholder) {
79
111
  const fieldEl = document.querySelector(selector);
80
112
  if (fieldEl) {
@@ -12,6 +12,19 @@ export const I18nDefaults = {
12
12
  "translateFields.select": "Select",
13
13
  "translateFields.recipientTo": "To:",
14
14
  "a11y.errorSummary": "There are {count} errors: {messages}.",
15
+ // InputPlaceholders component defaults
16
+ "placeholders.firstName": "First Name",
17
+ "placeholders.lastName": "Last Name",
18
+ "placeholders.emailAddress": "Email Address",
19
+ "placeholders.phoneNumber": "Phone Number",
20
+ "placeholders.phoneNumberOptional": "Phone Number (Optional)",
21
+ "placeholders.phoneNumber2Optional": "000-000-0000 (Optional)",
22
+ "placeholders.country": "Country",
23
+ "placeholders.address1": "Street Address",
24
+ "placeholders.address2": "Apt., Ste., Bldg.",
25
+ "placeholders.city": "City",
26
+ "placeholders.region": "Region",
27
+ "placeholders.postcode": "ZIP Code",
15
28
  },
16
29
  es: {
17
30
  "rememberMe.label": "Recuérdame",
@@ -26,5 +39,18 @@ export const I18nDefaults = {
26
39
  "translateFields.select": "Seleccione",
27
40
  "translateFields.recipientTo": "Para:",
28
41
  "a11y.errorSummary": "Hay {count} errores: {messages}.",
42
+ // InputPlaceholders component defaults
43
+ "placeholders.firstName": "Nombre",
44
+ "placeholders.lastName": "Apellidos",
45
+ "placeholders.emailAddress": "Correo electrónico",
46
+ "placeholders.phoneNumber": "Teléfono",
47
+ "placeholders.phoneNumberOptional": "Teléfono (opcional)",
48
+ "placeholders.phoneNumber2Optional": "000-000-0000 (opcional)",
49
+ "placeholders.country": "País",
50
+ "placeholders.address1": "Calle y número",
51
+ "placeholders.address2": "Depto., Piso, Edif.",
52
+ "placeholders.city": "Ciudad",
53
+ "placeholders.region": "Provincia/Estado",
54
+ "placeholders.postcode": "Código Postal",
29
55
  },
30
56
  };
@@ -2,7 +2,6 @@ import { EnForm, RememberMeEvents } from "./events";
2
2
  export declare class RememberMe {
3
3
  _form: EnForm;
4
4
  _events: RememberMeEvents;
5
- private _frequency;
6
5
  private remoteUrl;
7
6
  private cookieName;
8
7
  private fieldNames;
@@ -15,7 +14,6 @@ export declare class RememberMe {
15
14
  private fieldDonationAmountRadioName;
16
15
  private fieldDonationAmountOtherName;
17
16
  private fieldDonationRecurrPayRadioName;
18
- private fieldDonationRecurrFreqRadioName;
19
17
  private fieldDonationAmountOtherCheckboxID;
20
18
  private fieldOptInSelectorTarget;
21
19
  private fieldOptInSelectorTargetLocation;
@@ -31,7 +29,6 @@ export declare class RememberMe {
31
29
  fieldDonationAmountRadioName?: string;
32
30
  fieldDonationAmountOtherName?: string;
33
31
  fieldDonationRecurrPayRadioName?: string;
34
- fieldDonationRecurrFreqRadioName?: string;
35
32
  fieldDonationAmountOtherCheckboxID?: string;
36
33
  fieldOptInSelectorTarget?: string;
37
34
  fieldOptInSelectorTargetLocation?: string;
@@ -104,25 +101,5 @@ export declare class RememberMe {
104
101
  * @param overwrite - A boolean indicating whether to overwrite the existing value of the fields. Defaults to false.
105
102
  */
106
103
  private writeFields;
107
- /**
108
- * SwapAmounts replaces the donationAmt radio DOM nodes ~1 second after page
109
- * load (triggered by DonationFrequency.load() setTimeout). When that happens
110
- * the selection the RememberMe just wrote gets wiped out.
111
- *
112
- * This method subscribes to the first onFrequencyChange event and, after a
113
- * short delay to let SwapAmounts finish its DOM update, re-applies only the
114
- * donation amount. It unsubscribes immediately so it only fires once.
115
- *
116
- * To avoid overwriting a manual donor interaction, the handler checks
117
- * whether the current amount selection is empty/wiped (as SwapAmounts does)
118
- * OR still matches what writeFields originally set. If the donor already
119
- * picked a different amount, we skip re-application.
120
- */
121
- private reapplyDonationAmtAfterSwap;
122
- /**
123
- * Returns the currently selected donation amount value, or null if nothing
124
- * is selected. Checks both predefined radio buttons and the "Other" text input.
125
- */
126
- private getCurrentSelectedAmount;
127
104
  private isJson;
128
105
  }
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import * as cookie from "./cookie";
11
- import { EnForm, RememberMeEvents, DonationFrequency } from "./events";
11
+ import { EnForm, RememberMeEvents } from "./events";
12
12
  import { ENGrid } from "./engrid";
13
13
  const tippy = require("tippy.js").default;
14
14
  // localStorage key used to cache the per-device AES-GCM encryption key.
@@ -18,7 +18,6 @@ export class RememberMe {
18
18
  constructor(options) {
19
19
  this._form = EnForm.getInstance();
20
20
  this._events = RememberMeEvents.getInstance();
21
- this._frequency = DonationFrequency.getInstance();
22
21
  this.iframe = null;
23
22
  this.encryptData = options.encryptData ? options.encryptData : false;
24
23
  this.hide = options.hide ? options.hide : false;
@@ -41,10 +40,6 @@ export class RememberMe {
41
40
  options.fieldDonationRecurrPayRadioName
42
41
  ? options.fieldDonationRecurrPayRadioName
43
42
  : "transaction.recurrpay";
44
- this.fieldDonationRecurrFreqRadioName =
45
- options.fieldDonationRecurrFreqRadioName
46
- ? options.fieldDonationRecurrFreqRadioName
47
- : "transaction.recurrfreq";
48
43
  this.fieldDonationAmountOtherCheckboxID =
49
44
  options.fieldDonationAmountOtherCheckboxID
50
45
  ? options.fieldDonationAmountOtherCheckboxID
@@ -106,7 +101,6 @@ export class RememberMe {
106
101
  }
107
102
  else {
108
103
  this.insertClearRememberMeLink();
109
- this.reapplyDonationAmtAfterSwap();
110
104
  }
111
105
  }
112
106
  });
@@ -126,9 +120,6 @@ export class RememberMe {
126
120
  this.insertClearRememberMeLink();
127
121
  }
128
122
  this.writeFields();
129
- if (hasFieldData) {
130
- this.reapplyDonationAmtAfterSwap();
131
- }
132
123
  this._form.onSubmit.subscribe(() => {
133
124
  if (this.rememberMeOptIn) {
134
125
  this.readFields();
@@ -147,9 +138,6 @@ export class RememberMe {
147
138
  this.insertClearRememberMeLink();
148
139
  }
149
140
  this.writeFields();
150
- if (hasFieldData) {
151
- this.reapplyDonationAmtAfterSwap();
152
- }
153
141
  this._form.onSubmit.subscribe(() => {
154
142
  if (this.rememberMeOptIn) {
155
143
  this.readFields();
@@ -483,17 +471,6 @@ export class RememberMe {
483
471
  if (type === "radio" || type === "checkbox") {
484
472
  field = document.querySelector(fieldSelector + ":checked");
485
473
  }
486
- // When the donation amount radio is set to "Other", save the actual
487
- // custom value from the .other text input instead of "Other".
488
- if (this.fieldNames[i] === this.fieldDonationAmountRadioName &&
489
- field &&
490
- field.value.toLowerCase() === "other") {
491
- const otherField = document.querySelector("input[name='" + this.fieldDonationAmountOtherName + "']");
492
- if (otherField && otherField.value) {
493
- this.fieldData[this.fieldNames[i]] = encodeURIComponent(otherField.value);
494
- continue;
495
- }
496
- }
497
474
  this.fieldData[this.fieldNames[i]] = encodeURIComponent(field.value);
498
475
  }
499
476
  else if (field.tagName === "SELECT") {
@@ -577,36 +554,17 @@ export class RememberMe {
577
554
  field.click();
578
555
  }
579
556
  }
580
- else if (this.fieldNames[i] === this.fieldDonationRecurrFreqRadioName) {
581
- // recurrfreq is a radio group — find the specific radio with the saved value and click it
582
- const savedValue = this.fieldData[this.fieldNames[i]];
583
- if (savedValue) {
584
- const freqRadio = document.querySelector(fieldSelector + "[value='" + CSS.escape(savedValue) + "']");
585
- if (freqRadio) {
586
- freqRadio.click();
587
- }
588
- }
589
- }
590
557
  else if (this.fieldDonationAmountRadioName === this.fieldNames[i]) {
591
- const savedAmt = this.fieldData[this.fieldNames[i]];
592
- const escapedAmt = CSS.escape(savedAmt);
593
- field = document.querySelector(fieldSelector + "[value='" + escapedAmt + "']");
558
+ field = document.querySelector(fieldSelector +
559
+ "[value='" +
560
+ this.fieldData[this.fieldNames[i]] +
561
+ "']");
594
562
  if (field) {
595
- // Saved value matches a predefined radio option — just click it
596
563
  field.click();
597
564
  }
598
565
  else {
599
- // No matching radio: the value is a custom amount.
600
- // Click the "Other" radio first so the text input becomes active,
601
- // then fill in the numeric value.
602
- const otherRadio = document.querySelector(fieldSelector + "[value='Other'], " +
603
- fieldSelector + "[value='other'], " +
604
- fieldSelector + "[value='OTHER']");
605
- if (otherRadio) {
606
- otherRadio.click();
607
- }
608
- const otherField = document.querySelector("input[name='" + this.fieldDonationAmountOtherName + "']");
609
- this.setFieldValue(otherField, savedAmt, true);
566
+ field = document.querySelector("input[name='" + this.fieldDonationAmountOtherName + "']");
567
+ this.setFieldValue(field, this.fieldData[this.fieldNames[i]], true);
610
568
  }
611
569
  }
612
570
  else {
@@ -619,74 +577,6 @@ export class RememberMe {
619
577
  }
620
578
  }
621
579
  }
622
- /**
623
- * SwapAmounts replaces the donationAmt radio DOM nodes ~1 second after page
624
- * load (triggered by DonationFrequency.load() setTimeout). When that happens
625
- * the selection the RememberMe just wrote gets wiped out.
626
- *
627
- * This method subscribes to the first onFrequencyChange event and, after a
628
- * short delay to let SwapAmounts finish its DOM update, re-applies only the
629
- * donation amount. It unsubscribes immediately so it only fires once.
630
- *
631
- * To avoid overwriting a manual donor interaction, the handler checks
632
- * whether the current amount selection is empty/wiped (as SwapAmounts does)
633
- * OR still matches what writeFields originally set. If the donor already
634
- * picked a different amount, we skip re-application.
635
- */
636
- reapplyDonationAmtAfterSwap() {
637
- const savedAmt = this.fieldData[this.fieldDonationAmountRadioName];
638
- if (!savedAmt)
639
- return;
640
- // Capture the amount that writeFields just set so we can detect manual changes
641
- const amountAtRegistration = this.getCurrentSelectedAmount();
642
- const handler = () => {
643
- // SwapAmounts calls _amount.load() after swapList — give it a tick to settle
644
- window.setTimeout(() => {
645
- const currentAmt = this.getCurrentSelectedAmount();
646
- // Only re-apply if the selection is now empty (DOM was swapped out)
647
- // or still matches what we originally wrote. If the donor manually
648
- // selected a different amount, respect their choice.
649
- const selectionWiped = currentAmt === null || currentAmt === "";
650
- const selectionUnchanged = currentAmt === amountAtRegistration;
651
- if (!selectionWiped && !selectionUnchanged) {
652
- return;
653
- }
654
- const fieldSelector = "[name='" + this.fieldDonationAmountRadioName + "']";
655
- const escapedAmt = CSS.escape(savedAmt);
656
- let radio = document.querySelector(fieldSelector + "[value='" + escapedAmt + "']");
657
- if (radio) {
658
- radio.click();
659
- }
660
- else {
661
- // Custom amount: click "Other" radio then fill the text input
662
- const otherRadio = document.querySelector(fieldSelector + "[value='Other'], " +
663
- fieldSelector + "[value='other'], " +
664
- fieldSelector + "[value='OTHER']");
665
- if (otherRadio)
666
- otherRadio.click();
667
- const otherField = document.querySelector("input[name='" + this.fieldDonationAmountOtherName + "']");
668
- this.setFieldValue(otherField, savedAmt, true);
669
- }
670
- }, 200);
671
- };
672
- // Subscribe once: fires on the first frequency change then auto-unsubscribes
673
- this._frequency.onFrequencyChange.one(handler);
674
- }
675
- /**
676
- * Returns the currently selected donation amount value, or null if nothing
677
- * is selected. Checks both predefined radio buttons and the "Other" text input.
678
- */
679
- getCurrentSelectedAmount() {
680
- const fieldSelector = "[name='" + this.fieldDonationAmountRadioName + "']";
681
- const checkedRadio = document.querySelector(fieldSelector + ":checked");
682
- if (!checkedRadio)
683
- return null;
684
- if (checkedRadio.value.toLowerCase() === "other") {
685
- const otherField = document.querySelector("input[name='" + this.fieldDonationAmountOtherName + "']");
686
- return otherField ? otherField.value : null;
687
- }
688
- return checkedRadio.value;
689
- }
690
580
  isJson(str) {
691
581
  try {
692
582
  JSON.parse(str);
@@ -134,7 +134,20 @@ export class TranslateFields {
134
134
  ? simplecountriesSelect.cloneNode(true)
135
135
  : null;
136
136
  if (field instanceof HTMLInputElement && field.placeholder != "") {
137
- if (!fieldLabel || fieldLabel.innerHTML == field.placeholder) {
137
+ // Translate the placeholder when it mirrors the label (the common
138
+ // case). Compare normalized visible text so template whitespace and
139
+ // required-marker markup don't break the match. Order matters:
140
+ // trim before stripping the marker, or labels like "Name *\n"
141
+ // keep the asterisk.
142
+ const labelText = ((fieldLabel === null || fieldLabel === void 0 ? void 0 : fieldLabel.textContent) || "")
143
+ .replace(/\s+/g, " ")
144
+ .trim()
145
+ .replace(/\s*\*$/, "");
146
+ const placeholderText = field.placeholder
147
+ .replace(/\s+/g, " ")
148
+ .trim()
149
+ .replace(/\s*\*$/, "");
150
+ if (!fieldLabel || labelText === placeholderText) {
138
151
  field.dataset.original = field.placeholder;
139
152
  field.placeholder = translation;
140
153
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AppVersion = "0.28.0";
1
+ export declare const AppVersion = "0.28.2";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const AppVersion = "0.28.0";
1
+ export const AppVersion = "0.28.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4site/engrid-scripts",
3
- "version": "0.28.0",
3
+ "version": "0.28.2",
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": "354dfda54591f849c4ab105d881034f7f7a2fff5",
28
+ "gitHead": "16aba8b38f4a978d1f965272f2df6bf6b98dd9f8",
29
29
  "devDependencies": {
30
30
  "nodemon": "^2.0.20",
31
31
  "typescript": "^4.9.5"