@rebilly/instruments 3.12.3-beta.0 → 3.13.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +43 -66
  3. package/dist/index.min.js +43 -66
  4. package/package.json +3 -6
  5. package/src/functions/destroy.js +8 -2
  6. package/src/functions/mount/fetch-data.js +9 -2
  7. package/src/functions/mount/index.js +15 -10
  8. package/src/functions/mount/mount.spec.js +10 -11
  9. package/src/functions/mount/setup-framepay-theme.js +30 -72
  10. package/src/functions/mount/setup-options.js +2 -2
  11. package/src/functions/mount/{setup-styles.js → setup-styles-vars.js} +9 -7
  12. package/src/functions/purchase.js +2 -5
  13. package/src/functions/setup.js +3 -6
  14. package/src/functions/show.js +2 -2
  15. package/src/functions/show.spec.js +4 -4
  16. package/src/functions/update.spec.js +4 -3
  17. package/src/instance.js +4 -1
  18. package/src/loader/index.js +57 -33
  19. package/src/storefront/index.js +2 -5
  20. package/src/storefront/payment-instruments.js +7 -0
  21. package/src/style/base/__snapshots__/theme.spec.js.snap +136 -220
  22. package/src/style/base/default-theme.js +187 -14
  23. package/src/style/base/index.js +487 -79
  24. package/src/style/base/theme.js +3 -4
  25. package/src/style/base/theme.spec.js +2 -3
  26. package/src/style/index.js +4 -24
  27. package/src/style/utils/color-values.js +1 -1
  28. package/src/style/utils/minifyCss.js +14 -0
  29. package/src/views/common/iframe/base-iframe.js +3 -2
  30. package/src/views/common/iframe/event-listeners.js +12 -9
  31. package/src/views/common/iframe/method-iframe.js +3 -1
  32. package/src/views/common/iframe/modal-iframe.js +4 -2
  33. package/src/views/common/iframe/view-iframe.js +3 -1
  34. package/src/views/confirmation.js +12 -7
  35. package/src/views/errors.js +95 -0
  36. package/src/views/method-selector/express-methods.js +51 -0
  37. package/src/views/method-selector/generate-framepay-config.js +54 -0
  38. package/src/views/method-selector/generate-framepay-config.spec.js +195 -0
  39. package/src/views/method-selector/get-payment-methods.js +0 -1
  40. package/src/views/method-selector/index.js +45 -58
  41. package/src/views/method-selector/method-selector.spec.js +1 -1
  42. package/src/views/method-selector/mount-express-methods.js +66 -26
  43. package/src/views/modal.js +1 -1
  44. package/src/views/result.js +3 -3
  45. package/src/views/summary.js +24 -190
  46. package/tests/mocks/storefront-api-mock.js +48 -27
  47. package/src/style/browserslist.js +0 -1
  48. package/src/style/components/accordion.js +0 -140
  49. package/src/style/components/address.js +0 -55
  50. package/src/style/components/button.js +0 -117
  51. package/src/style/components/divider.js +0 -39
  52. package/src/style/components/forms/checkbox.js +0 -75
  53. package/src/style/components/forms/field.js +0 -56
  54. package/src/style/components/forms/form.js +0 -18
  55. package/src/style/components/forms/input.js +0 -77
  56. package/src/style/components/forms/label.js +0 -55
  57. package/src/style/components/forms/radio.js +0 -80
  58. package/src/style/components/forms/select.js +0 -86
  59. package/src/style/components/forms/validation.js +0 -72
  60. package/src/style/components/icons.js +0 -13
  61. package/src/style/components/index.js +0 -39
  62. package/src/style/components/loader.js +0 -41
  63. package/src/style/components/methods.js +0 -97
  64. package/src/style/components/overlay.js +0 -24
  65. package/src/style/helpers/index.js +0 -54
  66. package/src/style/payment-instruments/content.js +0 -8
  67. package/src/style/payment-instruments/index.js +0 -14
  68. package/src/style/payment-instruments/payment-card.js +0 -27
  69. package/src/style/payment-instruments/payment-instrument-list.js +0 -44
  70. package/src/style/payment-instruments/payment-instrument.js +0 -55
  71. package/src/style/utils/remove-empty-null.js +0 -10
  72. package/src/style/vendor/framepay.js +0 -28
  73. package/src/style/vendor/postmate.js +0 -18
  74. package/src/style/views/confirmation.js +0 -26
  75. package/src/style/views/index.js +0 -16
  76. package/src/style/views/method-selector.js +0 -11
  77. package/src/style/views/modal.js +0 -91
  78. package/src/style/views/result.js +0 -52
  79. package/src/style/views/summary.js +0 -118
  80. package/src/views/__snapshots__/summary.spec.js.snap +0 -246
  81. package/src/views/method-selector/express-methods/apple-pay.js +0 -92
  82. package/src/views/method-selector/express-methods/index.js +0 -25
  83. package/src/views/method-selector/mount-methods.js +0 -178
  84. package/src/views/summary.spec.js +0 -145
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@rebilly/instruments",
3
- "version": "3.12.3-beta.0",
3
+ "version": "3.13.2-beta.0",
4
4
  "author": "Rebilly",
5
+ "license": "MIT",
5
6
  "main": "dist/index.js",
6
7
  "unpkg": "dist/index.min.js",
7
- "license": "MIT",
8
8
  "scripts": {
9
9
  "build": "yarn rollup -c --environment NODE_ENV:production",
10
10
  "dev": "yarn rollup -c --watch --environment NODE_ENV:development",
@@ -16,16 +16,13 @@
16
16
  "@babel/core": "^7.14.6",
17
17
  "@babel/preset-env": "^7.14.7",
18
18
  "@rebilly/risk-data-collector": "^2.3.0",
19
- "autoprefixer": "^10.3.4",
20
- "css": "^3.0.0",
21
19
  "jwt-decode": "^3.1.2",
22
20
  "lodash.camelcase": "^4.3.0",
23
21
  "lodash.isequal": "^4.5.0",
24
22
  "lodash.kebabcase": "^4.1.1",
25
23
  "lodash.merge": "^4.6.2",
26
24
  "popostmate": "^1.6.4",
27
- "postcss": "^8.4.5",
28
- "rebilly-js-sdk": "^44.4.0",
25
+ "rebilly-js-sdk": "^47.3.1",
29
26
  "values.js": "^2.0.0"
30
27
  },
31
28
  "devDependencies": {
@@ -6,13 +6,19 @@ export async function destroy({ state }) {
6
6
  // wait to allow for cancellation to catch any pending api requests
7
7
  const sleepMilliseconds = 1000;
8
8
  await sleep(sleepMilliseconds);
9
- [...(state.iframeComponents || {})].forEach((iframe) => {
9
+ [
10
+ ...(state.iframeComponents?.form || []),
11
+ ...(state.iframeComponents?.summary || [])
12
+ ].forEach((iframe) => {
10
13
  iframe.destroy();
11
14
  });
12
15
 
13
16
  registeredListeners.removeAll(document);
14
17
 
15
- state.iframeComponents = [];
18
+ state.iframeComponents = {
19
+ summary: [],
20
+ form: [],
21
+ };
16
22
  state.hasMounted = false;
17
23
 
18
24
  state.summary.textContent = '';
@@ -6,6 +6,7 @@ import { fetchSummary } from '../../storefront/summary';
6
6
  import { fetchInvoice as FetchInvoice } from '../../storefront/invoices';
7
7
  import { fetchTransaction as FetchTransaction } from '../../storefront/transactions';
8
8
  import { fetchAccount as FetchAccount } from '../../storefront/account';
9
+ import { fetchPaymentInstrument as FetchInstruments } from '../../storefront/payment-instruments';
9
10
 
10
11
  export class DataInstance {
11
12
  constructor({
@@ -108,11 +109,13 @@ export async function fetchData({
108
109
  // Dependency injectable functions
109
110
  fetchInvoice = FetchInvoice,
110
111
  fetchTransaction = FetchTransaction,
111
- fetchAccount = FetchAccount
112
+ fetchAccount = FetchAccount,
113
+ fetchInstruments = FetchInstruments
112
114
  }) {
113
115
  try {
114
116
  let transaction = null;
115
117
  let account = null;
118
+ let availableInstruments = null;
116
119
  if (state.options?.transactionId) {
117
120
  transaction = await fetchTransaction({data: {
118
121
  id: state.options.transactionId
@@ -134,7 +137,10 @@ export async function fetchData({
134
137
  }
135
138
 
136
139
  if (state.options?.jwt) {
137
- account = await fetchAccount({state});
140
+ [account, availableInstruments] = await Promise.all([
141
+ fetchAccount({state}),
142
+ fetchInstruments({state})
143
+ ]);
138
144
  }
139
145
 
140
146
  state.data = new DataInstance({
@@ -143,6 +149,7 @@ export async function fetchData({
143
149
  transaction,
144
150
  riskMetadata,
145
151
  account,
152
+ availableInstruments,
146
153
  });
147
154
 
148
155
  const [readyToPay, previewPurchase] = await Promise.all([
@@ -7,10 +7,11 @@ import setupElement from './setup-element';
7
7
  import setupStorefront from './setup-storefront';
8
8
  import setupOptions from './setup-options';
9
9
  import setupFramepayInstance from './setup-framepay';
10
- import setupStyles from './setup-styles';
10
+ import setupStylesVars from './setup-styles-vars';
11
11
  import setupI18n from './setup-i18n';
12
12
  import setupFramepayTheme from './setup-framepay-theme';
13
13
  import setupUserFlow from './setup-user-flow';
14
+ import { showError } from '../../views/errors';
14
15
 
15
16
  /**
16
17
  * @typedef {object} Item
@@ -87,23 +88,26 @@ export async function mount({
87
88
  // Setup DOM
88
89
  state.form = setupElement({ element: 'form', options });
89
90
  state.summary = setupElement({ element: 'summary', options });
91
+ // hardcode max-width to not break UX
92
+ // PayPal button cannot exceed 750px;
93
+ state.form.style.maxWidth = '750px';
94
+ state.summary.style.maxWidth = '750px';
95
+
96
+ // Setup state
97
+ state.options = setupOptions({ options });
98
+ state.storefront = setupStorefront({ options });
99
+ state.mainStyleVars = setupStylesVars({ options });
100
+ state.options.themeFramepay = setupFramepayTheme({ state, options });
90
101
 
91
102
  // Setup loader
92
103
  state.loader.addDOMElement({ el: state.form });
93
104
  state.loader.addDOMElement({ section: 'summary', el: state.summary });
94
- state.loader.startLoading({ section: 'summary', id: 'initSummary' });
95
- state.loader.startLoading({ id: 'initForm' });
105
+ state.loader.startLoading({ state, section: 'summary', id: 'rebilly-instruments-summary' });
106
+ state.loader.startLoading({ state, id: 'rebilly-instruments-methods' });
96
107
 
97
- // Setup state
98
- state.options = setupOptions({ options });
99
- state.storefront = setupStorefront({ options });
100
-
101
- state.mainStyle = await setupStyles({ options });
102
-
103
108
  state.data = await fetchData({ state });
104
109
  Events.dataReady.dispatch(state.data);
105
110
 
106
- state.options.themeFramepay = await setupFramepayTheme({ state, options });
107
111
  state.i18n = setupI18n({ state });
108
112
 
109
113
  await setupFramepay(state);
@@ -128,6 +132,7 @@ export async function mount({
128
132
  state.i18n({state});
129
133
  state.hasMounted = true;
130
134
  } catch (error) {
135
+ showError(error);
131
136
  throw error;
132
137
  }
133
138
 
@@ -29,23 +29,22 @@ describe('RebillyInstruments instance', () => {
29
29
  };
30
30
  await RenderMockRebillyInstruments(options);
31
31
 
32
+ await new Promise((resolve) => {
33
+ setTimeout(() => {
34
+ resolve();
35
+ }, 100);
36
+ })
37
+
32
38
  // Mounts form and summary
33
39
  const summarySelector = document.querySelector('.summary-selector');
34
- expect(summarySelector.innerHTML).toMatch('My Product');
40
+ expect(summarySelector.innerHTML).toContain('<iframe name="rebilly-instruments-summary" class="rebilly-instruments-iframe" loading="lazy" allow="payment" scrolling="no" src="https://forms.local.rebilly.dev:3000/summary"></iframe>');
35
41
 
36
42
  // Theme config overrides initial styles
37
- const SUMMARY_CONTAINER = summarySelector.querySelector(
38
- '.rebilly-instruments-content'
39
- );
40
- expect(getComputedStyle(SUMMARY_CONTAINER).getPropertyValue('--rebilly-colorBackground')).toEqual(
41
- '#000'
42
- );
43
+ const STYLE_VARS = document.querySelectorAll('style[type="text/css"]')[0];
43
44
 
44
- // CSS config property overrides initial styles
45
- const LINE_ITEM_TITLE = document.querySelector(
46
- '.rebilly-instruments-summary-line-item-synopsis-title'
45
+ expect(STYLE_VARS.innerHTML).toMatch(
46
+ '--rebilly-colorBackground: #000;'
47
47
  );
48
- expect(getComputedStyle(LINE_ITEM_TITLE).color).toEqual('rgb(0, 68, 212)');
49
48
 
50
49
  // Mounts default FramePay script
51
50
  const SCRIPTS = [...document.querySelectorAll('head script')];
@@ -1,6 +1,3 @@
1
- import css from 'css';
2
- import camelCase from 'lodash.camelcase';
3
-
4
1
  function processCSS(rawCss) {
5
2
  const cssMap = {};
6
3
  [...rawCss.matchAll(/(--rebilly.*(?=:))[:\s](.*(?=;))/g)]
@@ -26,101 +23,62 @@ function processCSS(rawCss) {
26
23
  }
27
24
 
28
25
  function replaceCssVars(rawCss) {
26
+ const CssVarsObj = {}
29
27
  processCSS(rawCss).forEach(([key, value]) => {
30
- rawCss = rawCss.split(`var(${key})`).join(value);
28
+ CssVarsObj[key] = value;
31
29
  });
32
-
33
- return rawCss;
30
+ return CssVarsObj;
34
31
  }
35
32
 
36
- const getStyleProps = (ast, selector) => {
37
- const { rules } = ast.stylesheet;
33
+ const getStyleProps = (obj, particle = '') => {
38
34
  const output = {
39
- color: null,
40
- fontFamily: null,
41
- fontSize: null,
42
- lineHeight: null,
43
- fontWeight: null,
44
- background: null
45
- }
46
-
47
- for(let i = 0; i < rules.length; i += 1) {
48
- const rule = rules[i];
49
-
50
- if(rule.type === 'rule' && rule.selectors.includes(selector)) {
51
- rule.declarations.forEach(decl => {
52
- const propName = camelCase(decl.property);
53
- if(typeof output[propName] !== 'undefined') {
54
- output[propName] = decl.value;
55
- }
56
- })
57
- }
35
+ color: obj[`--rebilly-${particle}ColorText`],
36
+ fontFamily: obj[`--rebilly-${particle}FontFamily`],
37
+ fontSize: obj[`--rebilly-${particle}FontSize`],
38
+ fontWeight: obj[`--rebilly-${particle}FontWeight`],
39
+ lineHeight: obj[`--rebilly-${particle}FontLineHeight`],
40
+ background: obj[`--rebilly-${particle}ColorBackground`],
41
+ boxShadow: obj[`--rebilly-${particle}BoxShadow`],
58
42
  }
59
43
  return output;
60
44
  }
61
45
 
62
- export default async ({
46
+ export default ({
63
47
  state,
64
48
  options = {}
65
49
  }) => {
66
50
  const fullCss = `
67
- ${state.mainStyle}
51
+ ${state.mainStyleVars}
68
52
  ${options.css || ''}
69
53
  `;
70
54
 
71
- const resolvedCss = replaceCssVars(fullCss);
72
- const cssAst = css.parse(resolvedCss);
73
-
74
- const cssSelectors = {
75
- input: {
76
- base: '.rebilly-instruments-form-field-input',
77
- baseHover: '.rebilly-instruments-form-field-input:hover',
78
- baseFocus: '.rebilly-instruments-form-field-input:focus',
79
- basePlaceholder: '.rebilly-instruments-form-field-input::placeholder',
80
- baseSelection: '.rebilly-instruments-form-field-input::selection',
81
-
82
- invalid: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input',
83
- invalidHover: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:hover',
84
- invalidFocus: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:focus',
85
- invalidPlaceholder: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::placeholder',
86
- invalidSelection: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::selection'
87
- },
88
- button: {
89
- base: '.rebilly-instruments-button.rebilly-instruments-button-secondary',
90
- baseHover: '.rebilly-instruments-button.rebilly-instruments-button-secondary:not([disabled]):hover',
91
- baseFocus: '.rebilly-instruments-button.rebilly-instruments-button-secondary:not([disabled]):active',
92
-
93
- active: '.rebilly-instruments-button',
94
- activeHover: '.rebilly-instruments-button:not([disabled]):hover',
95
- activeFocus: '.rebilly-instruments-button:not([disabled]):active',
96
- }
97
- }
55
+ const resolvedCssVarsObj = replaceCssVars(fullCss);
98
56
 
99
57
  const framepayStyle = {
100
58
  base: {
101
- ...getStyleProps(cssAst, cssSelectors.input.base),
102
- ':hover': getStyleProps(cssAst, cssSelectors.input.baseHover),
103
- ':focus': getStyleProps(cssAst, cssSelectors.input.baseFocus),
104
- '::placeholder': getStyleProps(cssAst, cssSelectors.input.basePlaceholder),
105
- '::selection': getStyleProps(cssAst, cssSelectors.input.baseSelection),
59
+ ...getStyleProps(resolvedCssVarsObj, 'input'),
60
+ ':hover': getStyleProps(resolvedCssVarsObj, 'inputHover'),
61
+ ':focus': getStyleProps(resolvedCssVarsObj, 'inputFocus'),
62
+ '::placeholder': getStyleProps(resolvedCssVarsObj, 'inputPlaceholder'),
63
+ '::selection': getStyleProps(resolvedCssVarsObj, 'inputSelection'),
106
64
  },
107
65
  invalid: {
108
- ...getStyleProps(cssAst, cssSelectors.input.invalid),
109
- ':hover': getStyleProps(cssAst, cssSelectors.input.invalidHover),
110
- ':focus': getStyleProps(cssAst, cssSelectors.input.invalidFocus),
111
- '::placeholder': getStyleProps(cssAst, cssSelectors.input.invalidPlaceholder),
112
- '::selection': getStyleProps(cssAst, cssSelectors.input.invalidSelection),
66
+ ...getStyleProps(resolvedCssVarsObj, 'inputError'),
67
+ ':hover': getStyleProps(resolvedCssVarsObj, 'inputErrorHover'),
68
+ ':focus': getStyleProps(resolvedCssVarsObj, 'inputErrorFocus'),
69
+ '::placeholder': getStyleProps(resolvedCssVarsObj, 'inputErrorPlaceholder'),
70
+ '::selection': getStyleProps(resolvedCssVarsObj, 'inputErrorSelection'),
113
71
  },
114
72
  buttons: {
115
73
  base: {
116
- ...getStyleProps(cssAst, cssSelectors.button.base),
117
- ':hover': getStyleProps(cssAst, cssSelectors.button.baseHover),
118
- ':focus': getStyleProps(cssAst, cssSelectors.button.baseFocus),
74
+ ...getStyleProps(resolvedCssVarsObj, 'buttonSecondary'),
75
+ ':hover': getStyleProps(resolvedCssVarsObj, 'buttonSecondaryHover'),
76
+ ':focus': getStyleProps(resolvedCssVarsObj, 'buttonSecondaryActive'),
119
77
  },
120
78
  active: {
121
- ...getStyleProps(cssAst, cssSelectors.button.active),
122
- ':hover': getStyleProps(cssAst, cssSelectors.button.activeHover),
123
- ':focus': getStyleProps(cssAst, cssSelectors.button.activeFocus),
79
+ ...getStyleProps(resolvedCssVarsObj, 'button'),
80
+ ':hover': getStyleProps(resolvedCssVarsObj, 'buttonHover'),
81
+ ':focus': getStyleProps(resolvedCssVarsObj, 'buttonActive'),
124
82
  }
125
83
  }
126
84
  }
@@ -5,7 +5,7 @@ export const defaults = {
5
5
  countryCode: 'US',
6
6
  locale: 'auto',
7
7
  theme: {
8
- labels: 'floating'
8
+ labels: 'stacked'
9
9
  },
10
10
  paymentInstruments: {
11
11
  address: {
@@ -15,7 +15,7 @@ export const defaults = {
15
15
  show: [],
16
16
  require: []
17
17
  },
18
- compactExpressInstruments: true,
18
+ compactExpressInstruments: false,
19
19
  googlePay: {
20
20
  displayOptions: {
21
21
  buttonColor: 'black',
@@ -1,18 +1,20 @@
1
- import { mainStyle } from '../../style';
1
+ import { mainStyleVars } from '../../style';
2
2
  import { addDOMElement } from '../../utils';
3
+ import { minifyCss } from '../../style/utils/minifyCss';
3
4
 
4
- export default async ({
5
+ export default ({
5
6
  options: {
6
7
  theme = {},
7
8
  css,
8
9
  }
9
10
  } = {}) => {
10
- // Adds base stylesheet
11
- const style = await mainStyle(theme || {});
11
+ // Adds base CSS vars stylesheet
12
+ const styleVars = mainStyleVars(theme || {});
13
+
12
14
  addDOMElement({
13
15
  element: 'style',
14
16
  attributes: { type: 'text/css' },
15
- content: style,
17
+ content: minifyCss(styleVars),
16
18
  target: 'head'
17
19
  });
18
20
 
@@ -21,10 +23,10 @@ export default async ({
21
23
  addDOMElement({
22
24
  element: 'style',
23
25
  attributes: { type: 'text/css' },
24
- content: css,
26
+ content: minifyCss(css),
25
27
  target: 'head'
26
28
  });
27
29
  }
28
30
 
29
- return style;
31
+ return styleVars;
30
32
  }
@@ -2,6 +2,7 @@ import { postPurchase, postPayment } from '../storefront/purchase';
2
2
  import Events from '../events';
3
3
  import { mountModal } from '../views/modal';
4
4
  import { DataInstance } from './mount/fetch-data';
5
+ import { showError } from '../views/errors';
5
6
 
6
7
  export async function makePayment({ state, payload }) {
7
8
  const {
@@ -109,11 +110,7 @@ export async function purchase({ state, payload }) {
109
110
  }
110
111
  return fields;
111
112
  } catch (error) {
112
- // TODO: Display error to customer
113
- console.error(error);
114
- if(error.status === 422) {
115
- error.details.forEach(e => console.error(e));
116
- }
113
+ showError(error);
117
114
  return error;
118
115
  }
119
116
  }
@@ -2,7 +2,8 @@ import { setupPaymentInstrument } from '../storefront/payment-instruments';
2
2
  import Events from '../events';
3
3
  import { mountModal } from '../views/modal';
4
4
  import { DataInstance } from './mount/fetch-data';
5
-
5
+ import { showError } from '../views/errors';
6
+
6
7
  export async function setup({ state, payload }) {
7
8
  try {
8
9
  const {
@@ -56,11 +57,7 @@ export async function setup({ state, payload }) {
56
57
 
57
58
  return {instrument, transaction};
58
59
  } catch (error) {
59
- // TODO: Display error to customer
60
- console.error(error);
61
- if(error.status === 422) {
62
- error.details.forEach(e => console.error(e));
63
- }
60
+ showError(error);
64
61
  return error;
65
62
  }
66
63
  }
@@ -16,14 +16,14 @@ import { mountResult } from '../views/result';
16
16
  export async function show({ componentName, payload, state }) {
17
17
  switch (componentName) {
18
18
  case 'result':
19
- state.iframeComponents = state.iframeComponents.filter((iframe) => {
19
+ state.iframeComponents.form = state.iframeComponents.form.filter((iframe) => {
20
20
  iframe.destroy();
21
21
  return false;
22
22
  });
23
23
  mountResult({payload, state});
24
24
  break;
25
25
  case 'confirmation':
26
- state.iframeComponents = state.iframeComponents.filter((iframe) => {
26
+ state.iframeComponents.form = state.iframeComponents.form.filter((iframe) => {
27
27
  iframe.destroy();
28
28
  return false;
29
29
  });
@@ -12,7 +12,7 @@ describe('RebillyInstruments show', () => {
12
12
  .mockReturnValue(Promise.resolve());
13
13
 
14
14
  const instance = new RebillyInstrumentsInstance();
15
- instance.state.iframeComponents.push(iframeMock);
15
+ instance.state.iframeComponents.form.push(iframeMock);
16
16
 
17
17
  const payload = {
18
18
  test: 'value'
@@ -22,7 +22,7 @@ describe('RebillyInstruments show', () => {
22
22
 
23
23
  expect(mountResult).toBeCalledTimes(1);
24
24
  expect(mountResult).toBeCalledWith({payload, state: instance.state});
25
- expect(instance.state.iframeComponents).toEqual([]);
25
+ expect(instance.state.iframeComponents.form).toEqual([]);
26
26
  });
27
27
 
28
28
  it('should show confirmation component', async () => {
@@ -31,7 +31,7 @@ describe('RebillyInstruments show', () => {
31
31
  .mockReturnValue(Promise.resolve());
32
32
 
33
33
  const instance = new RebillyInstrumentsInstance();
34
- instance.state.iframeComponents.push(iframeMock);
34
+ instance.state.iframeComponents.form.push(iframeMock);
35
35
 
36
36
  const payload = {
37
37
  test: 'value'
@@ -41,7 +41,7 @@ describe('RebillyInstruments show', () => {
41
41
 
42
42
  expect(mountConfirmation).toBeCalledTimes(1);
43
43
  expect(mountConfirmation).toBeCalledWith({payload, state: instance.state});
44
- expect(instance.state.iframeComponents).toEqual([]);
44
+ expect(instance.state.iframeComponents.form).toEqual([]);
45
45
  });
46
46
 
47
47
  it('should fail for non supported component', async () => {
@@ -31,7 +31,7 @@ describe('RebillyInstruments Update', () => {
31
31
  call
32
32
  }
33
33
  };
34
- rebillyInstruments.state.iframeComponents = [fakeIFrameComponent];
34
+ rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
35
35
 
36
36
  await rebillyInstruments.update({ locale: 'ja' });
37
37
 
@@ -58,7 +58,7 @@ describe('RebillyInstruments Update', () => {
58
58
  call
59
59
  }
60
60
  };
61
- rebillyInstruments.state.iframeComponents = [fakeIFrameComponent];
61
+ rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
62
62
 
63
63
  await rebillyInstruments.update({
64
64
  countryCode: 'ES'
@@ -83,7 +83,8 @@ describe('RebillyInstruments Update', () => {
83
83
  call
84
84
  }
85
85
  };
86
- rebillyInstruments.iframeComponents = [fakeIFrameComponent];
86
+
87
+ rebillyInstruments.state.iframeComponents.form = [fakeIFrameComponent];
87
88
 
88
89
  await rebillyInstruments.update({
89
90
  items: [
package/src/instance.js CHANGED
@@ -18,7 +18,10 @@ export class InstrumentsState {
18
18
  this.summary = null;
19
19
  this.loader = new Loader();
20
20
  this.translate = new Translate();
21
- this.iframeComponents = [];
21
+ this.iframeComponents = {
22
+ summary: [],
23
+ form: [],
24
+ };
22
25
  this.hasMounted = false;
23
26
  }
24
27
  }
@@ -1,54 +1,86 @@
1
1
  import isDOMElement from '../utils/is-dom-element';
2
2
 
3
- export const loaderHTML = `
4
- <div class="rebilly-instruments-loader is-active">
5
- <div class="rebilly-instruments-loader-spinner"></div>
6
- <span class="rebilly-instuments-loader-message"></span>
3
+ export const summaryLoaderHTML = `
4
+ <div class="rebilly-instruments-summary-loader-total rebilly-instruments-loader-display-flex rebilly-instruments-loader-align-center rebilly-instruments-loader-justify-space-between">
5
+ <div><p class="is-el-loading">Total</p></div>
6
+ <p class="total is-el-loading">$99.99</p>
7
7
  </div>
8
+ `;
9
+
10
+ export const methodsLoaderHTML = `
11
+ <div class="rebilly-instruments-methods-loader">
12
+ <div class="rebilly-instruments-loader-form-el is-el-loading"></div>
13
+ <div class="rebilly-instruments-divider">
14
+ <span class="rebilly-instruments-divider-label"><span class="is-el-loading">Divi</span></span>
15
+ </div>
16
+ <div class="rebilly-instruments-loader-display-flex rebilly-instruments-loader-justify-end">
17
+ <div class="rebilly-instruments-methods-loader-card-icon is-el-loading"></div>
18
+ <div class="rebilly-instruments-methods-loader-card-icon is-el-loading"></div>
19
+ </div>
20
+ <div class="rebilly-instruments-loader-form-el is-el-loading"></div>
21
+ <div class="rebilly-instruments-methods-loader-form-fields">
22
+ <div class="rebilly-instruments-loader-form-label"><small class="is-el-loading">Card holder name</small></div>
23
+ <div class="rebilly-instruments-loader-form-el is-el-loading"></div>
24
+ </div>
25
+ <div class="rebilly-instruments-loader-form-el is-button">Continue</div>
26
+ </div>
27
+ `;
28
+
29
+ const loaderContainer = (padding = '0px') => `
30
+ <div class="rebilly-instruments-loader is-active" style="padding: ${padding}"></div>
8
31
  `
9
32
 
33
+ export const basicLoaderHTML = `
34
+ <div class="rebilly-instruments-loader-spinner"></div>
35
+ `;
36
+
10
37
  export class Loader {
11
38
  constructor() {
12
39
  this.summary = [];
13
40
  this.form = [];
14
41
  this.modal = [];
15
42
  this.DOM = {
16
- loading: loaderHTML
43
+ loaderContainer,
44
+ summaryLoaderHTML,
45
+ methodsLoaderHTML,
46
+ basicLoaderHTML
17
47
  };
18
48
  }
19
49
 
20
50
  addDOMElement({ section = 'form', el = null } = {}) {
21
51
  if (isDOMElement(el)) {
22
52
  el.style.position = 'relative';
23
-
24
53
  this.DOM[section] = el;
25
54
  }
26
55
  }
27
56
 
28
- startLoading({state, section = 'form', id = '', message = null } = {}) {
29
- const minHeight = '200px';
57
+ startLoading({section = 'form', id = ''} = {}) {
30
58
  this[section].push(id);
31
59
 
60
+ const rootEl = document.querySelector(':root');
61
+ const contentPadding = 2;
62
+ let minHeight = '240px';
63
+
32
64
  if (this.DOM?.[section]) {
33
- if (!this.DOM[section].querySelector('.rebilly-instruments-loader')) {
34
- this.DOM[section].innerHTML = this.DOM.loading;
65
+ let loaderEl = this.DOM[section].querySelector('.rebilly-instruments-loader');
66
+ const { padding, paddingTop, paddingBottom } = getComputedStyle(this.DOM?.[section]);
67
+ if (!loaderEl) {
68
+ this.DOM[section].innerHTML = loaderContainer(`${parseFloat(padding) + contentPadding}px`);
69
+ loaderEl = this.DOM[section].querySelector('.rebilly-instruments-loader');
70
+ if (section === 'form') {
71
+ loaderEl.insertAdjacentHTML('afterbegin', this.DOM.methodsLoaderHTML);
72
+ minHeight = getComputedStyle(rootEl).getPropertyValue('--rebilly-methodLoaderMinHeight');
73
+ } else if (section === 'summary') {
74
+ loaderEl.insertAdjacentHTML('afterbegin', this.DOM.summaryLoaderHTML);
75
+ minHeight = getComputedStyle(rootEl).getPropertyValue('--rebilly-summaryLoaderMinHeight');
76
+ }
35
77
  } else {
36
- this.DOM[section]
37
- .querySelector('.rebilly-instruments-loader')
38
- .classList.add('is-active');
39
- }
40
- this.DOM[section].style.minHeight = minHeight;
41
- }
42
-
43
- if (message) {
44
- const loaderMessage = this.DOM[section].querySelector('.rebilly-instuments-loader-message');
45
- if (loaderMessage) {
46
- loaderMessage.innerHTML = message;
47
- loaderMessage.style.marginTop = '10px';
48
- loaderMessage.setAttribute('data-message-id', id);
49
- loaderMessage.setAttribute('data-rebilly-i18n', message);
50
- state.i18n({state});
78
+ if (id.includes('confirmation') || id.includes('result') || id.includes('modal')) {
79
+ loaderEl.innerHTML = this.DOM.basicLoaderHTML;
80
+ }
81
+ loaderEl.classList.add('is-active');
51
82
  }
83
+ this.DOM[section].style.minHeight = `calc(${minHeight} + ${paddingTop} + ${paddingBottom} + ${contentPadding * 2}px)`;
52
84
  }
53
85
  }
54
86
 
@@ -68,14 +100,6 @@ export class Loader {
68
100
  .classList.remove('is-active');
69
101
  this.DOM[section].style.minHeight = '';
70
102
  }
71
-
72
- const loaderMessage = this.DOM[section].querySelector(`.rebilly-instuments-loader-message[data-message-id="${id}"]`);
73
- if (loaderMessage) {
74
- loaderMessage.innerHTML = null;
75
- loaderMessage.style.marginTop = '0px';
76
- loaderMessage.removeAttribute('data-message-id');
77
- loaderMessage.removeAttribute('data-rebilly-i18n');
78
- }
79
103
  }
80
104
 
81
105
  clearAll() {