@amos.com/amos-js 0.9.11 → 0.9.13

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/README.md CHANGED
@@ -14,7 +14,7 @@ npm install @amos.com/amos-js
14
14
 
15
15
  - **Types** for the `postMessage` protocol used between your page and the Amos iframe (`Message`, `Appearance`, `ThemeVariable`). OpenAPI schema types (for example `components["schemas"]["PaymentIntent"]`) come from `@amos.com/node`.
16
16
  - **Iframe-targeted helpers** to validate the form, confirm a payment intent, confirm a setup intent, update appearance, etc.
17
- - **Mount functions** (`mountAmosCreditCardPaymentMethodForm`, `mountAmosBankAccountPaymentMethodForm`, `mountAmosGooglePayButton`, `mountAmosApplePayButton`) that create the iframe, wire up its message protocol, manage its height/opacity, and return a small controller for updating options and tearing it down.
17
+ - **Mount functions** (`mountAmosCreditCardPaymentMethodForm`, `mountAmosBankAccountPaymentMethodForm`, `mountAmosGooglePayButton`, `mountAmosApplePayButton`) that create the iframe, wire up its message protocol, manage its height/opacity, show a field-shaped loading skeleton for card/bank forms, and return a small controller for updating options and tearing it down.
18
18
  - **Lower-level building blocks** (`getCreditCardFormSrc`, `attachPaymentMethodFormListeners`, `attachGooglePayButtonListeners`, `attachApplePayButtonListeners`, ...) for integrators (such as `@amos.com/react-amos-js`) that want to render the iframe element themselves.
19
19
 
20
20
  > **Note:** A server-side SDK (for example `@amos.com/node`) must be used alongside `@amos.com/amos-js` for end-to-end payment processing. `@amos.com/amos-js` is the client-side half.
@@ -96,7 +96,7 @@ form.destroy();
96
96
  The following flow is for credit card and bank account payment method types only.
97
97
 
98
98
  1. **Set up prerequisites**: create a `renderToken` (safe for client), and keep `apiKey` and `accountId` server-side only.
99
- 2. **Render your checkout UI** by calling `mountAmosCreditCardPaymentMethodForm(container, options)` (or `mountAmosBankAccountPaymentMethodForm(...)`) along with the required `onResult` callback. The iframe height is auto-managed by the SDK.
99
+ 2. **Render your checkout UI** by calling `mountAmosCreditCardPaymentMethodForm(container, options)` (or `mountAmosBankAccountPaymentMethodForm(...)`) along with the required `onResult` callback. The SDK shows a field-shaped skeleton immediately (sized from `appearance`, `additionalFields`, and `billingAddressRequirement`) and auto-manages iframe height.
100
100
  3. **User clicks "Pay now" button**: call `validateForm({ iframe: form.iframe })`, which returns `Promise<true>` if the embedded form is valid, and `Promise<false>` otherwise.
101
101
  4. **Create payment intent on your server**: use your server-side Amos client to call `POST /payment_intents`. You may also associate this payment intent with a new or existing customer via `POST /customers`. This must be server-side because it uses your private API key.
102
102
  5. **Return the payment intent token to the browser**: your backend responds with the embed token (`components["schemas"]["EmbedToken"]`) needed for confirmation.
@@ -268,13 +268,13 @@ Mount the secure credit-card payment method form into a container element (an `H
268
268
 
269
269
 
270
270
  - `onValidityChange` (`(event: { isValid: boolean }) => void`) — called when form validity changes. `isValid` is true when all required fields are present and valid. Does not include PCI data. Use this to enable or disable your checkout button.
271
- - `onHeightChange`, `onAppearanceReady` (advanced — override the default iframe styling logic)
271
+ - `onHeightChange`, `onAppearanceReady` (advanced — override the default iframe styling logic). The skeleton is removed and the iframe faded in when `onAppearanceReady` fires.
272
272
 
273
273
  **Returns** `AmosPaymentMethodFormMountController`:
274
274
 
275
275
  - `iframe` — the underlying `<iframe>` element.
276
276
  - `update(patch)` — patch any of the options listed above.
277
- - `destroy()` — remove the iframe and detach listeners.
277
+ - `destroy()` — remove the iframe (and any loading skeleton) and detach listeners.
278
278
 
279
279
  ### `mountAmosBankAccountPaymentMethodForm(container, options)`
280
280
 
@@ -295,13 +295,28 @@ Mount the secure Google Pay button (express checkout) into a container element.
295
295
 
296
296
  **Optional `options`:** `appearance`, `onHeightChange`, `onAppearanceReady`, plus Google Pay button visual options using the same names as `@google-pay/button-react`:
297
297
 
298
+ - `fullWidth` (`boolean`, defaults to `false`) — fills the mount container without changing the compact default
298
299
  - `buttonType` (`"book" | "buy" | "checkout" | "donate" | "order" | "pay" | "plain" | "subscribe" | "short" | "long"`, defaults to `"short"`)
299
300
  - `buttonColor` (`"default" | "black" | "white"`)
300
301
  - `buttonRadius` (`number`, 0–20)
301
302
  - `buttonSizeMode` (`"static" | "fill"`)
302
303
  - `buttonLocale` (`string`, e.g. `"en"`)
303
304
  - `buttonBorderType` (`"no_border" | "default_border"`)
304
- - `style` (`{ [property: string]: string | number }`) — applied to the Google Pay button inside the iframe (e.g. `{ height: "48px", width: "100%" }`). Combined with `buttonSizeMode: "fill"` to stretch the button.
305
+ - `style` (`{ [property: string]: string | number }`) — applied to the Google Pay button inside the iframe (e.g. `{ height: "48px" }`)
306
+
307
+ For a full-width 48px button:
308
+
309
+ ```ts
310
+ mountAmosGooglePayButton("#google-pay", {
311
+ // ...required options
312
+ fullWidth: true,
313
+ style: { height: "48px" },
314
+ });
315
+ ```
316
+
317
+ The wallet iframe itself is flush with its mount container (`width: 100%`,
318
+ zero margin). `style` targets the button inside that iframe. For advanced host
319
+ layout overrides, use the returned controller's `iframe` element.
305
320
 
306
321
  **Returns** `AmosGooglePayButtonMountController`:
307
322
 
@@ -313,6 +328,7 @@ Mount the secure Apple Pay button (express checkout). Same required options and
313
328
 
314
329
  **Optional visual options** use Apple's `<apple-pay-button>` attribute names:
315
330
 
331
+ - `fullWidth` (`boolean`, defaults to `false`) — fills the mount container without changing the compact default
316
332
  - `buttonstyle` (`"black" | "white" | "white-outline"`, defaults to `"black"`)
317
333
  - `type` (`"plain" | "buy" | "set-up" | "donate" | "check-out" | "book" | "subscribe" | "reload" | "add-money" | "top-up" | "order" | "rent" | "support" | "contribute" | "tip"`, defaults to `"plain"`)
318
334
  - `locale` (`string`, BCP 47, defaults to `"en-US"`)
@@ -320,13 +336,12 @@ Mount the secure Apple Pay button (express checkout). Same required options and
320
336
 
321
337
  ```ts
322
338
  button.update({
339
+ fullWidth: true,
323
340
  buttonstyle: "white-outline",
324
341
  type: "buy",
325
342
  locale: "en-GB",
326
343
  style: {
327
344
  "--apple-pay-button-height": "48px",
328
- "--apple-pay-button-width": "100%",
329
- width: "100%",
330
345
  },
331
346
  });
332
347
  ```
@@ -0,0 +1,20 @@
1
+ import { BillingAddressRequirement, CreditCardAdditionalFields } from './payment-method-form';
2
+ import { Appearance } from './types';
3
+ export type PaymentMethodFormSkeletonKind = "card" | "bank";
4
+ export type PaymentMethodFormSkeletonOptions = {
5
+ kind: PaymentMethodFormSkeletonKind;
6
+ appearance?: Appearance;
7
+ additionalFields?: CreditCardAdditionalFields;
8
+ billingAddressRequirement?: BillingAddressRequirement;
9
+ };
10
+ export type PaymentMethodFormSkeleton = {
11
+ element: HTMLElement;
12
+ update: (options: PaymentMethodFormSkeletonOptions) => void;
13
+ };
14
+ /**
15
+ * Host-page placeholder that mirrors card/bank field layout using the
16
+ * same appearance variables the iframe will apply. Shown immediately
17
+ * while the iframe document loads, then removed when appearance is
18
+ * ready.
19
+ */
20
+ export declare function createPaymentMethodFormSkeleton(options: PaymentMethodFormSkeletonOptions): PaymentMethodFormSkeleton;
package/dist/index.js CHANGED
@@ -1 +1,64 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`data-amos-apple-pay-waiting`;function t({onCancel:t}){let n=document.querySelector(`[${e}]`);if(n)return n;let r=document.createElement(`div`);r.setAttribute(e,`true`),r.setAttribute(`role`,`dialog`),r.setAttribute(`aria-modal`,`true`),r.setAttribute(`aria-labelledby`,`amos-apple-pay-waiting-title`),Object.assign(r.style,{position:`fixed`,inset:`0`,zIndex:`2147483646`,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,boxSizing:`border-box`,background:`rgba(0, 0, 0, 0.55)`,fontFamily:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`});let i=document.createElement(`div`);Object.assign(i.style,{width:`100%`,maxWidth:`360px`,borderRadius:`12px`,background:`#fff`,padding:`28px 24px 20px`,boxSizing:`border-box`,textAlign:`center`,boxShadow:`0 12px 40px rgba(0, 0, 0, 0.25)`});let a=document.createElement(`div`);a.setAttribute(`aria-hidden`,`true`),Object.assign(a.style,{display:`inline-flex`,alignItems:`center`,justifyContent:`center`,gap:`6px`,marginBottom:`16px`,fontSize:`28px`,fontWeight:`600`,letterSpacing:`-0.02em`,color:`#000`,lineHeight:`1`}),a.innerHTML=`<svg width="22" height="26" viewBox="0 0 814 1000" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg><span>Pay</span>`;let o=document.createElement(`p`);o.id=`amos-apple-pay-waiting-title`,Object.assign(o.style,{margin:`0 0 20px`,fontSize:`15px`,lineHeight:`1.45`,color:`#1a1a1a`}),o.textContent=`Complete your payment in the open Apple Pay window, or close Apple Pay to continue paying another way.`;let s=document.createElement(`button`);return s.type=`button`,s.textContent=`Cancel payment`,Object.assign(s.style,{display:`block`,width:`100%`,border:`none`,borderRadius:`8px`,padding:`12px 16px`,background:`#2c2c2e`,color:`#fff`,fontSize:`15px`,fontWeight:`500`,cursor:`pointer`}),s.addEventListener(`click`,t),i.append(a,o,s),r.append(i),document.body.append(r),r}function n(){document.querySelector(`[${e}]`)?.remove()}function r(e){let[t=``,n=``,r=``]=e?.split(`.`)??[],i=typeof atob==`function`?atob:e=>Buffer.from(e,`base64`).toString(`utf8`);return{header:JSON.parse(i(t)),payload:JSON.parse(i(n)),signature:r}}function i(e){let{env:t=`sandbox`}=r(e).payload;switch(t){case`production`:return`https://embed.amos.com`;case`sandbox`:return`https://embed-sandbox.amos.com`;default:return`https://embed-sandbox.amos.com`}}function a(e){if(!e||typeof e!=`object`)return;let t={};for(let[n,r]of Object.entries(e)){if(typeof r==`string`){if(r.length===0)continue;t[n]=r;continue}typeof r==`number`&&Number.isFinite(r)&&(t[n]=r)}return Object.keys(t).length>0?t:void 0}function o(e){let t={};if(e.buttonstyle!==void 0&&(t.buttonstyle=e.buttonstyle),e.type!==void 0&&(t.type=e.type),e.locale!==void 0&&(t.locale=e.locale),e.style!==void 0){let n=a(e.style);n&&(t.style=n)}return t}function s(e){let t={};if(e.buttonType!==void 0&&(t.buttonType=e.buttonType),e.buttonColor!==void 0&&(t.buttonColor=e.buttonColor),e.buttonRadius!==void 0&&(t.buttonRadius=e.buttonRadius),e.buttonSizeMode!==void 0&&(t.buttonSizeMode=e.buttonSizeMode),e.buttonLocale!==void 0&&(t.buttonLocale=e.buttonLocale),e.buttonBorderType!==void 0&&(t.buttonBorderType=e.buttonBorderType),e.style!==void 0){let n=a(e.style);n&&(t.style=n)}return t}function c(e){return e}function l(e){return new URL(e.src).origin}function u(e){e?.contentWindow&&e.contentWindow.postMessage(c({type:`PARENT_ACKNOWLEDGED_IFRAME_READY`}),l(e))}function d({iframe:e,appearance:t={}}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_APPEARANCE`,appearance:t}),l(e))}function f({iframe:e,props:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_APPLE_PAY_BUTTON`,props:o(t)}),l(e))}function p({iframe:e,props:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_GOOGLE_PAY_BUTTON`,props:s(t)}),l(e))}function m({iframe:e,amount:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_AMOUNT`,amount:t}),l(e))}function h({iframe:e,merchantName:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_MERCHANT_NAME`,merchantName:t}),l(e))}function g({iframe:e}){let t=crypto.randomUUID();return new Promise(n=>{e?.contentWindow&&e.contentWindow.postMessage(c({type:`VALIDATE_FORM`,requestId:t}),l(e));let r=setTimeout(()=>{window.removeEventListener(`message`,i),n(!1)},5e3);function i(e){e.data.type===`VALIDATE_FORM`&&e.data.requestId===t&&(window.removeEventListener(`message`,i),clearTimeout(r),n(e.data.isValid??!1))}window.addEventListener(`message`,i)})}function _({iframe:e}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`RESET_FORM`}),l(e))}function v({iframe:e,token:t}){if(!e?.contentWindow)return;let{payment_intent_id:n}=r(t).payload;e.contentWindow.postMessage(c({type:`CONFIRM_PAYMENT_INTENT`,token:t,id:n??void 0}),l(e))}function y({iframe:e,token:t}){if(!e?.contentWindow)return;let{setup_intent_id:n}=r(t).payload;e.contentWindow.postMessage(c({type:`CONFIRM_SETUP_INTENT`,token:t,id:n??void 0}),l(e))}function b({iframe:e,result:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`CONFIRMATION_RESULT`,result:t}),l(e))}function x(e){return`${i(e)}/iframe/apple-pay?token=${e}`}function S(){return`40px`}function C(e){e.contentWindow?.postMessage(c({type:`APPLE_PAY_CANCEL`}),l(e))}function w(e,r){let i={...r};function a(){m({iframe:e,amount:i.amount})}function o(){h({iframe:e,merchantName:i.merchantName})}function s(){f({iframe:e,props:i})}function c(r){if(r.source===e.contentWindow)switch(r.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:i.appearance}),a(),o(),s();break;case`UPDATE_HEIGHT`:i.onHeightChange?.(r.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:r.data.appearance});break;case`UPDATED_APPEARANCE`:i.onAppearanceReady?.();break;case`APPLE_PAY_WINDOW_OPEN`:t({onCancel:()=>{C(e),n()}});break;case`APPLE_PAY_WINDOW_CLOSE`:n();break;case`CREATE_PAYMENT_INTENT`:i.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:r.data.paymentIntentCreateAttributes,customerCreateAttributes:r.data.customerCreateAttributes}).then(t=>{v({iframe:e,token:t})}).catch(e=>{n(),i.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:n(),i.onResult(r.data.result)}}return window.addEventListener(`message`,c),{update(t){let n=`appearance`in t,r=`amount`in t,c=`merchantName`in t,l=`buttonstyle`in t||`type`in t||`locale`in t||`style`in t;i={...i,...t},n&&d({iframe:e,appearance:i.appearance}),r&&a(),c&&o(),l&&s()},destroy(){window.removeEventListener(`message`,c),n()}}}function T(e){return`${i(e)}/iframe/google-pay?token=${e}`}function E(){return`40px`}function D(e,t){let n={...t};function r(){m({iframe:e,amount:n.amount})}function i(){h({iframe:e,merchantName:n.merchantName})}function a(){p({iframe:e,props:n})}function o(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:n.appearance}),r(),i(),a();break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{v({iframe:e,token:t})}).catch(e=>{n.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,o),{update(t){let o=`appearance`in t,s=`amount`in t,c=`merchantName`in t,l=`buttonType`in t||`buttonColor`in t||`buttonRadius`in t||`buttonSizeMode`in t||`buttonLocale`in t||`buttonBorderType`in t||`style`in t;n={...n,...t},o&&d({iframe:e,appearance:n.appearance}),s&&r(),c&&i(),l&&a()},destroy(){window.removeEventListener(`message`,o)}}}function O({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_payload:e.paymentMethodData.tokenizationData.token}}}}var k={country:212,full:452},A=80,j={country:400,full:640};function M(e,t={cardholderName:!1},n=`country`){let r=Object.entries(t).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:r,billingAddressRequirement:n});return`${i(e)}/iframe/card?${a}`}function N(e,t=`country`){let n=new URLSearchParams({token:e,billingAddressRequirement:t});return`${i(e)}/iframe/bank?${n}`}function P(e={cardholderName:!1},t=`country`){return`${(k[t]??k.country)+(e.cardholderName?A:0)}px`}function F(e=`country`){return`${j[e]??j.country}px`}function I(e,t){let n={...t};function r(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`FORM_VALIDITY_CHANGE`:n.onValidityChange?.({isValid:t.data.isValid});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,r),{update(t){let r=`appearance`in t;n={...n,...t},r&&d({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,r)}}}function L(e){if(typeof e==`string`){let t=document.querySelector(e);if(!(t instanceof HTMLElement))throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);return t}return e}var R={width:`calc(100% + 8px)`,transition:`opacity 150ms ease-in, height 200ms ease-in-out`,margin:`0 -4px`,opacity:`0`,border:`0`};function z({src:e,title:t,name:n,height:r,allow:i}){let a=document.createElement(`iframe`);return a.src=e,a.title=t,a.name=n,a.setAttribute(`role`,`presentation`),a.scrolling=`no`,i&&(a.allow=i),Object.assign(a.style,R,{height:r}),a}function B(e,t){let n=L(e),{renderToken:r,additionalFields:i={cardholderName:!1},billingAddressRequirement:a=`country`,...o}=t,s=z({src:M(r,i,a),title:`Secure credit card payment method form powered by Amos`,name:`amos-credit-card-payment-method-form`,height:P(i,a)});n.appendChild(s);let c=I(s,{...o,onHeightChange:e=>{s.style.height=e,o.onHeightChange?.(e)},onAppearanceReady:()=>{s.style.opacity=`1`,o.onAppearanceReady?.()}});return{iframe:s,update:c.update,destroy(){c.destroy(),s.remove()}}}function V(e,t){let n=L(e),{renderToken:r,billingAddressRequirement:i=`country`,...a}=t,o=z({src:N(r,i),title:`Secure bank account payment method form powered by Amos`,name:`amos-bank-account-payment-method-form`,height:F(i)});n.appendChild(o);let s=I(o,{...a,onHeightChange:e=>{o.style.height=e,a.onHeightChange?.(e)},onAppearanceReady:()=>{o.style.opacity=`1`,a.onAppearanceReady?.()}});return{iframe:o,update:s.update,destroy(){s.destroy(),o.remove()}}}function H(e,t){let n=L(e),{renderToken:r,...i}=t,a=z({src:T(r),title:`Secure Google Pay button powered by Amos`,name:`amos-google-pay-button`,height:E(),allow:`payment`});n.appendChild(a);let o=D(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}function U(e,t){let n=L(e),{renderToken:r,...i}=t,a=z({src:x(r),title:`Secure Apple Pay button powered by Amos`,name:`amos-apple-pay-button`,height:S(),allow:`payment`});n.appendChild(a);let o=w(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}exports.attachApplePayButtonListeners=w,exports.attachGooglePayButtonListeners=D,exports.attachPaymentMethodFormListeners=I,exports.confirmPaymentIntent=v,exports.confirmSetupIntent=y,exports.createMessage=c,exports.decodeJwt=r,exports.formatGooglePayPaymentData=O,exports.getApplePayButtonInitialHeight=S,exports.getApplePayButtonSrc=x,exports.getBankAccountFormInitialHeight=F,exports.getBankAccountFormSrc=N,exports.getCreditCardFormInitialHeight=P,exports.getCreditCardFormSrc=M,exports.getEmbedOrigin=i,exports.getGooglePayButtonInitialHeight=E,exports.getGooglePayButtonSrc=T,exports.mountAmosApplePayButton=U,exports.mountAmosBankAccountPaymentMethodForm=V,exports.mountAmosCreditCardPaymentMethodForm=B,exports.mountAmosGooglePayButton=H,exports.pickApplePayButtonElementProps=o,exports.pickGooglePayButtonElementProps=s,exports.resetForm=_,exports.sendConfirmationResult=b,exports.sendParentReadyMessage=u,exports.serializeWalletButtonStyle=a,exports.updateAmount=m,exports.updateAppearance=d,exports.updateApplePayButton=f,exports.updateGooglePayButton=p,exports.updateMerchantName=h,exports.validateForm=g;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=`data-amos-apple-pay-waiting`;function t({onCancel:t}){let n=document.querySelector(`[${e}]`);if(n)return n;let r=document.createElement(`div`);r.setAttribute(e,`true`),r.setAttribute(`role`,`dialog`),r.setAttribute(`aria-modal`,`true`),r.setAttribute(`aria-labelledby`,`amos-apple-pay-waiting-title`),Object.assign(r.style,{position:`fixed`,inset:`0`,zIndex:`2147483646`,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,boxSizing:`border-box`,background:`rgba(0, 0, 0, 0.55)`,fontFamily:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`});let i=document.createElement(`div`);Object.assign(i.style,{width:`100%`,maxWidth:`360px`,borderRadius:`12px`,background:`#fff`,padding:`28px 24px 20px`,boxSizing:`border-box`,textAlign:`center`,boxShadow:`0 12px 40px rgba(0, 0, 0, 0.25)`});let a=document.createElement(`div`);a.setAttribute(`aria-hidden`,`true`),Object.assign(a.style,{display:`inline-flex`,alignItems:`center`,justifyContent:`center`,gap:`6px`,marginBottom:`16px`,fontSize:`28px`,fontWeight:`600`,letterSpacing:`-0.02em`,color:`#000`,lineHeight:`1`}),a.innerHTML=`<svg width="22" height="26" viewBox="0 0 814 1000" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg><span>Pay</span>`;let o=document.createElement(`p`);o.id=`amos-apple-pay-waiting-title`,Object.assign(o.style,{margin:`0 0 20px`,fontSize:`15px`,lineHeight:`1.45`,color:`#1a1a1a`}),o.textContent=`Complete your payment in the open Apple Pay window, or close Apple Pay to continue paying another way.`;let s=document.createElement(`button`);return s.type=`button`,s.textContent=`Cancel payment`,Object.assign(s.style,{display:`block`,width:`100%`,border:`none`,borderRadius:`8px`,padding:`12px 16px`,background:`#2c2c2e`,color:`#fff`,fontSize:`15px`,fontWeight:`500`,cursor:`pointer`}),s.addEventListener(`click`,t),i.append(a,o,s),r.append(i),document.body.append(r),r}function n(){document.querySelector(`[${e}]`)?.remove()}function r(e){let[t=``,n=``,r=``]=e?.split(`.`)??[],i=typeof atob==`function`?atob:e=>Buffer.from(e,`base64`).toString(`utf8`);return{header:JSON.parse(i(t)),payload:JSON.parse(i(n)),signature:r}}function i(e){let{env:t=`sandbox`}=r(e).payload;switch(t){case`production`:return`https://embed.amos.com`;case`sandbox`:return`https://embed-sandbox.amos.com`;default:return`https://embed-sandbox.amos.com`}}function a(e){if(!e||typeof e!=`object`)return;let t={};for(let[n,r]of Object.entries(e)){if(typeof r==`string`){if(r.length===0)continue;t[n]=r;continue}typeof r==`number`&&Number.isFinite(r)&&(t[n]=r)}return Object.keys(t).length>0?t:void 0}function o(e){let t={};if(e.fullWidth!==void 0&&(t.fullWidth=e.fullWidth),e.buttonstyle!==void 0&&(t.buttonstyle=e.buttonstyle),e.type!==void 0&&(t.type=e.type),e.locale!==void 0&&(t.locale=e.locale),e.style!==void 0){let n=a(e.style);n&&(t.style=n)}return t}function s(e){let t={};if(e.fullWidth!==void 0&&(t.fullWidth=e.fullWidth),e.buttonType!==void 0&&(t.buttonType=e.buttonType),e.buttonColor!==void 0&&(t.buttonColor=e.buttonColor),e.buttonRadius!==void 0&&(t.buttonRadius=e.buttonRadius),e.buttonSizeMode!==void 0&&(t.buttonSizeMode=e.buttonSizeMode),e.buttonLocale!==void 0&&(t.buttonLocale=e.buttonLocale),e.buttonBorderType!==void 0&&(t.buttonBorderType=e.buttonBorderType),e.style!==void 0){let n=a(e.style);n&&(t.style=n)}return t}function c(e){return e}function l(e){return new URL(e.src).origin}function u(e){e?.contentWindow&&e.contentWindow.postMessage(c({type:`PARENT_ACKNOWLEDGED_IFRAME_READY`}),l(e))}function d({iframe:e,appearance:t={}}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_APPEARANCE`,appearance:t}),l(e))}function f({iframe:e,props:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_APPLE_PAY_BUTTON`,props:o(t)}),l(e))}function p({iframe:e,props:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_GOOGLE_PAY_BUTTON`,props:s(t)}),l(e))}function m({iframe:e,amount:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_AMOUNT`,amount:t}),l(e))}function h({iframe:e,merchantName:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`UPDATE_MERCHANT_NAME`,merchantName:t}),l(e))}function g({iframe:e}){let t=crypto.randomUUID();return new Promise(n=>{e?.contentWindow&&e.contentWindow.postMessage(c({type:`VALIDATE_FORM`,requestId:t}),l(e));let r=setTimeout(()=>{window.removeEventListener(`message`,i),n(!1)},5e3);function i(e){e.data.type===`VALIDATE_FORM`&&e.data.requestId===t&&(window.removeEventListener(`message`,i),clearTimeout(r),n(e.data.isValid??!1))}window.addEventListener(`message`,i)})}function _({iframe:e}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`RESET_FORM`}),l(e))}function v({iframe:e,token:t}){if(!e?.contentWindow)return;let{payment_intent_id:n}=r(t).payload;e.contentWindow.postMessage(c({type:`CONFIRM_PAYMENT_INTENT`,token:t,id:n??void 0}),l(e))}function y({iframe:e,token:t}){if(!e?.contentWindow)return;let{setup_intent_id:n}=r(t).payload;e.contentWindow.postMessage(c({type:`CONFIRM_SETUP_INTENT`,token:t,id:n??void 0}),l(e))}function b({iframe:e,result:t}){e?.contentWindow&&e.contentWindow.postMessage(c({type:`CONFIRMATION_RESULT`,result:t}),l(e))}function x(e){return`${i(e)}/iframe/apple-pay?token=${e}`}function S(){return`40px`}function C(e){e.contentWindow?.postMessage(c({type:`APPLE_PAY_CANCEL`}),l(e))}function w(e,r){let i={...r};function a(){m({iframe:e,amount:i.amount})}function o(){h({iframe:e,merchantName:i.merchantName})}function s(){f({iframe:e,props:i})}function c(r){if(r.source===e.contentWindow)switch(r.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:i.appearance}),a(),o(),s();break;case`UPDATE_HEIGHT`:i.onHeightChange?.(r.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:r.data.appearance});break;case`UPDATED_APPEARANCE`:i.onAppearanceReady?.();break;case`APPLE_PAY_WINDOW_OPEN`:t({onCancel:()=>{C(e),n()}});break;case`APPLE_PAY_WINDOW_CLOSE`:n();break;case`CREATE_PAYMENT_INTENT`:i.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:r.data.paymentIntentCreateAttributes,customerCreateAttributes:r.data.customerCreateAttributes}).then(t=>{v({iframe:e,token:t})}).catch(e=>{n(),i.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:n(),i.onResult(r.data.result)}}return window.addEventListener(`message`,c),{update(t){let n=`appearance`in t,r=`amount`in t,c=`merchantName`in t,l=`fullWidth`in t||`buttonstyle`in t||`type`in t||`locale`in t||`style`in t;i={...i,...t},n&&d({iframe:e,appearance:i.appearance}),r&&a(),c&&o(),l&&s()},destroy(){window.removeEventListener(`message`,c),n()}}}function T(e){return`${i(e)}/iframe/google-pay?token=${e}`}function E(){return`40px`}function D(e,t){let n={...t};function r(){m({iframe:e,amount:n.amount})}function i(){h({iframe:e,merchantName:n.merchantName})}function a(){p({iframe:e,props:n})}function o(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:n.appearance}),r(),i(),a();break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{v({iframe:e,token:t})}).catch(e=>{n.onResult({status:`failed`,errorMessage:e instanceof Error?e.message:`Unknown error`})});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,o),{update(t){let o=`appearance`in t,s=`amount`in t,c=`merchantName`in t,l=`fullWidth`in t||`buttonType`in t||`buttonColor`in t||`buttonRadius`in t||`buttonSizeMode`in t||`buttonLocale`in t||`buttonBorderType`in t||`style`in t;n={...n,...t},o&&d({iframe:e,appearance:n.appearance}),s&&r(),c&&i(),l&&a()},destroy(){window.removeEventListener(`message`,o)}}}function O({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_payload:e.paymentMethodData.tokenizationData.token}}}}var k=`amos-js-form-skeleton-styles`,A={"--accent":`oklch(0.97 0 0)`,"--radius":`0.625rem`,"--input-height":`2.25rem`,"--floating-input-height":`3.25rem`,"--field-gap":`1rem`,"--control-gap":`0.5rem`,"--label-font-size":`0.875rem`},j=`
2
+ .amos-js-form-skeleton {
3
+ box-sizing: border-box;
4
+ container-type: inline-size;
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: var(--field-gap);
8
+ margin: 0 -4px;
9
+ padding-block: 0.25rem;
10
+ pointer-events: none;
11
+ width: calc(100% + 8px);
12
+ }
13
+ .amos-js-form-skeleton-field {
14
+ display: flex;
15
+ flex: 1 1 0;
16
+ flex-direction: column;
17
+ min-width: 0;
18
+ width: 100%;
19
+ }
20
+ .amos-js-form-skeleton-label {
21
+ flex-shrink: 0;
22
+ font-size: var(--label-font-size);
23
+ height: 1.75rem;
24
+ line-height: 1.75rem;
25
+ }
26
+ .amos-js-form-skeleton-input {
27
+ animation: amos-js-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
28
+ background: var(--accent);
29
+ border-radius: calc(var(--radius) * 0.8);
30
+ box-sizing: border-box;
31
+ height: var(--input-height);
32
+ width: 100%;
33
+ }
34
+ .amos-js-form-skeleton-input-floating {
35
+ height: var(--floating-input-height);
36
+ }
37
+ .amos-js-form-skeleton-row {
38
+ align-items: flex-start;
39
+ display: flex;
40
+ gap: var(--control-gap);
41
+ width: 100%;
42
+ }
43
+ .amos-js-form-skeleton-row-stack {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: var(--field-gap);
47
+ width: 100%;
48
+ }
49
+ @container (min-width: 24rem) {
50
+ .amos-js-form-skeleton-row-stack {
51
+ align-items: flex-start;
52
+ flex-direction: row;
53
+ gap: var(--control-gap);
54
+ }
55
+ }
56
+ @keyframes amos-js-skeleton-pulse {
57
+ 50% { opacity: 0.5; }
58
+ }
59
+ @media (prefers-reduced-motion: reduce) {
60
+ .amos-js-form-skeleton-input {
61
+ animation: none;
62
+ }
63
+ }
64
+ `;function M(){if(document.getElementById(k))return;let e=document.createElement(`style`);e.id=k,e.textContent=j,document.head.appendChild(e)}function N(e,t){for(let[t,n]of Object.entries(A))e.style.setProperty(t,n);let n=t?.themeVariables;if(n)for(let[t,r]of Object.entries(n))typeof r==`string`&&r.trim()!==``&&e.style.setProperty(t,r.trim())}function P(e,t){let n=document.createElement(`div`);if(n.className=e,t)for(let e of t)n.appendChild(e);return n}function F(e,t){let n=P(`amos-js-form-skeleton-field`);t!==void 0&&(n.style.flexGrow=String(t)),e===`above`&&n.appendChild(P(`amos-js-form-skeleton-label`));let r=P(`amos-js-form-skeleton-input`);return e===`floating`&&r.classList.add(`amos-js-form-skeleton-input-floating`),n.appendChild(r),n}function I(e,t){return P(t?`amos-js-form-skeleton-row-stack`:`amos-js-form-skeleton-row`,e)}function L({labels:e,requirement:t,wrapCountryZip:n}){return t===`full`?[F(e),F(e),I([F(e,1.4),F(e,.7),F(e,.8)],!1),F(e)]:[I([F(e),F(e)],n)]}function R(e){let t=e.appearance?.labels??`above`,n=e.billingAddressRequirement??`country`;if(e.kind===`card`){let r=[F(t),I([F(t),F(t)],!1)];return e.additionalFields?.cardholderName&&r.push(F(t)),r.push(...L({labels:t,requirement:n,wrapCountryZip:!1})),r}return[F(t),I([F(t),F(t)],!0),F(t),I([F(`above`),F(`above`)],!0),...L({labels:t,requirement:n,wrapCountryZip:!0})]}function z(e){M();let t=P(`amos-js-form-skeleton`);t.setAttribute(`aria-hidden`,`true`);function n(e){N(t,e.appearance),t.replaceChildren(...R(e))}return n(e),{element:t,update:n}}var B={country:212,full:452},V=80,H={country:400,full:640};function U(e,t={cardholderName:!1},n=`country`){let r=Object.entries(t).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:r,billingAddressRequirement:n});return`${i(e)}/iframe/card?${a}`}function W(e,t=`country`){let n=new URLSearchParams({token:e,billingAddressRequirement:t});return`${i(e)}/iframe/bank?${n}`}function G(e={cardholderName:!1},t=`country`){return`${(B[t]??B.country)+(e.cardholderName?V:0)}px`}function K(e=`country`){return`${H[e]??H.country}px`}function q(e,t){let n={...t};function r(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:u(e),d({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:d({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`FORM_VALIDITY_CHANGE`:n.onValidityChange?.({isValid:t.data.isValid});break;case`CONFIRMATION_RESULT`:n.onResult(t.data.result)}}return window.addEventListener(`message`,r),{update(t){let r=`appearance`in t;n={...n,...t},r&&d({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,r)}}}function J(e){if(typeof e==`string`){let t=document.querySelector(e);if(!(t instanceof HTMLElement))throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);return t}return e}var Y={width:`calc(100% + 8px)`,transition:`opacity 150ms ease-in, height 200ms ease-in-out`,margin:`0 -4px`,opacity:`0`,border:`0`},X={width:`100%`,transition:`height 200ms ease-in-out`,margin:`0`,opacity:`0`,border:`0`},Z={position:`absolute`,top:`0`,left:`-4px`,width:`calc(100% + 8px)`,height:`100%`,margin:`0`,transition:`none`,pointerEvents:`none`};function Q({src:e,title:t,name:n,height:r,allow:i,style:a=Y}){let o=document.createElement(`iframe`);return o.src=e,o.title=t,o.name=n,o.setAttribute(`role`,`presentation`),o.scrolling=`no`,i&&(o.allow=i),Object.assign(o.style,a,{height:r}),o}function $({host:e,iframe:t,listenerOptions:n,skeletonOptions:r}){let i=document.createElement(`div`);i.style.position=`relative`,i.style.width=`100%`,i.setAttribute(`aria-busy`,`true`);let a=z(r);Object.assign(t.style,Z),i.append(a.element,t),e.appendChild(i);let o=!1,s=!1,c,l=r.appearance,u,d;function f(){return i.getBoundingClientRect().height}function p(){return c?Number.parseFloat(c):NaN}function m(){if(o)return;o=!0,d!==void 0&&(clearTimeout(d),d=void 0);let e=f(),n=p(),r=Number.isFinite(n)?Math.max(n,e):e;t.style.transition=`none`,t.style.position=``,t.style.top=``,t.style.left=``,t.style.margin=Y.margin??``,t.style.height=`${r}px`,t.style.opacity=`1`,t.style.pointerEvents=``,a.element.remove(),i.removeAttribute(`aria-busy`),u=setTimeout(()=>{t.style.transition=`height 200ms ease-in-out`},400)}function h(){if(o||!s)return;let e=p(),t=f();Number.isFinite(e)&&e>=t-2&&m()}let g=q(t,{...n,onHeightChange:e=>{c=e,o?t.style.height=e:h(),n.onHeightChange?.(e)},onAppearanceReady:()=>{s=!0,h(),!o&&d===void 0&&(d=setTimeout(()=>{m()},1500)),n.onAppearanceReady?.()}});return{iframe:t,update(e){g.update(e),!o&&`appearance`in e&&(l=e.appearance,a.update({...r,appearance:l}))},destroy(){u!==void 0&&clearTimeout(u),d!==void 0&&clearTimeout(d),g.destroy(),i.remove()}}}function ee(e,t){let n=J(e),{renderToken:r,additionalFields:i={cardholderName:!1},billingAddressRequirement:a=`country`,...o}=t;return $({host:n,iframe:Q({src:U(r,i,a),title:`Secure credit card payment method form powered by Amos`,name:`amos-credit-card-payment-method-form`,height:G(i,a)}),listenerOptions:o,skeletonOptions:{kind:`card`,appearance:o.appearance,additionalFields:i,billingAddressRequirement:a}})}function te(e,t){let n=J(e),{renderToken:r,billingAddressRequirement:i=`country`,...a}=t;return $({host:n,iframe:Q({src:W(r,i),title:`Secure bank account payment method form powered by Amos`,name:`amos-bank-account-payment-method-form`,height:K(i)}),listenerOptions:a,skeletonOptions:{kind:`bank`,appearance:a.appearance,billingAddressRequirement:i}})}function ne(e,t){let n=J(e),{renderToken:r,...i}=t,a=Q({src:T(r),title:`Secure Google Pay button powered by Amos`,name:`amos-google-pay-button`,height:E(),allow:`payment`,style:X});n.appendChild(a);let o=D(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}function re(e,t){let n=J(e),{renderToken:r,...i}=t,a=Q({src:x(r),title:`Secure Apple Pay button powered by Amos`,name:`amos-apple-pay-button`,height:S(),allow:`payment`,style:X});n.appendChild(a);let o=w(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}exports.attachApplePayButtonListeners=w,exports.attachGooglePayButtonListeners=D,exports.attachPaymentMethodFormListeners=q,exports.confirmPaymentIntent=v,exports.confirmSetupIntent=y,exports.createMessage=c,exports.decodeJwt=r,exports.formatGooglePayPaymentData=O,exports.getApplePayButtonInitialHeight=S,exports.getApplePayButtonSrc=x,exports.getBankAccountFormInitialHeight=K,exports.getBankAccountFormSrc=W,exports.getCreditCardFormInitialHeight=G,exports.getCreditCardFormSrc=U,exports.getEmbedOrigin=i,exports.getGooglePayButtonInitialHeight=E,exports.getGooglePayButtonSrc=T,exports.mountAmosApplePayButton=re,exports.mountAmosBankAccountPaymentMethodForm=te,exports.mountAmosCreditCardPaymentMethodForm=ee,exports.mountAmosGooglePayButton=ne,exports.pickApplePayButtonElementProps=o,exports.pickGooglePayButtonElementProps=s,exports.resetForm=_,exports.sendConfirmationResult=b,exports.sendParentReadyMessage=u,exports.serializeWalletButtonStyle=a,exports.updateAmount=m,exports.updateAppearance=d,exports.updateApplePayButton=f,exports.updateGooglePayButton=p,exports.updateMerchantName=h,exports.validateForm=g;
package/dist/index.mjs CHANGED
@@ -99,7 +99,7 @@ function a(e) {
99
99
  }
100
100
  function o(e) {
101
101
  let t = {};
102
- if (e.buttonstyle !== void 0 && (t.buttonstyle = e.buttonstyle), e.type !== void 0 && (t.type = e.type), e.locale !== void 0 && (t.locale = e.locale), e.style !== void 0) {
102
+ if (e.fullWidth !== void 0 && (t.fullWidth = e.fullWidth), e.buttonstyle !== void 0 && (t.buttonstyle = e.buttonstyle), e.type !== void 0 && (t.type = e.type), e.locale !== void 0 && (t.locale = e.locale), e.style !== void 0) {
103
103
  let n = a(e.style);
104
104
  n && (t.style = n);
105
105
  }
@@ -107,7 +107,7 @@ function o(e) {
107
107
  }
108
108
  function s(e) {
109
109
  let t = {};
110
- if (e.buttonType !== void 0 && (t.buttonType = e.buttonType), e.buttonColor !== void 0 && (t.buttonColor = e.buttonColor), e.buttonRadius !== void 0 && (t.buttonRadius = e.buttonRadius), e.buttonSizeMode !== void 0 && (t.buttonSizeMode = e.buttonSizeMode), e.buttonLocale !== void 0 && (t.buttonLocale = e.buttonLocale), e.buttonBorderType !== void 0 && (t.buttonBorderType = e.buttonBorderType), e.style !== void 0) {
110
+ if (e.fullWidth !== void 0 && (t.fullWidth = e.fullWidth), e.buttonType !== void 0 && (t.buttonType = e.buttonType), e.buttonColor !== void 0 && (t.buttonColor = e.buttonColor), e.buttonRadius !== void 0 && (t.buttonRadius = e.buttonRadius), e.buttonSizeMode !== void 0 && (t.buttonSizeMode = e.buttonSizeMode), e.buttonLocale !== void 0 && (t.buttonLocale = e.buttonLocale), e.buttonBorderType !== void 0 && (t.buttonBorderType = e.buttonBorderType), e.style !== void 0) {
111
111
  let n = a(e.style);
112
112
  n && (t.style = n);
113
113
  }
@@ -277,7 +277,7 @@ function w(e, r) {
277
277
  }
278
278
  return window.addEventListener("message", c), {
279
279
  update(t) {
280
- let n = "appearance" in t, r = "amount" in t, c = "merchantName" in t, l = "buttonstyle" in t || "type" in t || "locale" in t || "style" in t;
280
+ let n = "appearance" in t, r = "amount" in t, c = "merchantName" in t, l = "fullWidth" in t || "buttonstyle" in t || "type" in t || "locale" in t || "style" in t;
281
281
  i = {
282
282
  ...i,
283
283
  ...t
@@ -360,7 +360,7 @@ function D(e, t) {
360
360
  }
361
361
  return window.addEventListener("message", o), {
362
362
  update(t) {
363
- let o = "appearance" in t, s = "amount" in t, c = "merchantName" in t, l = "buttonType" in t || "buttonColor" in t || "buttonRadius" in t || "buttonSizeMode" in t || "buttonLocale" in t || "buttonBorderType" in t || "style" in t;
363
+ let o = "appearance" in t, s = "amount" in t, c = "merchantName" in t, l = "fullWidth" in t || "buttonType" in t || "buttonColor" in t || "buttonRadius" in t || "buttonSizeMode" in t || "buttonLocale" in t || "buttonBorderType" in t || "style" in t;
364
364
  n = {
365
365
  ...n,
366
366
  ...t
@@ -392,15 +392,96 @@ function O({ paymentData: e }) {
392
392
  } };
393
393
  }
394
394
  //#endregion
395
+ //#region src/form-skeleton.ts
396
+ var k = "amos-js-form-skeleton-styles", A = {
397
+ "--accent": "oklch(0.97 0 0)",
398
+ "--radius": "0.625rem",
399
+ "--input-height": "2.25rem",
400
+ "--floating-input-height": "3.25rem",
401
+ "--field-gap": "1rem",
402
+ "--control-gap": "0.5rem",
403
+ "--label-font-size": "0.875rem"
404
+ }, j = "\n.amos-js-form-skeleton {\n box-sizing: border-box;\n container-type: inline-size;\n display: flex;\n flex-direction: column;\n gap: var(--field-gap);\n margin: 0 -4px;\n padding-block: 0.25rem;\n pointer-events: none;\n width: calc(100% + 8px);\n}\n.amos-js-form-skeleton-field {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n min-width: 0;\n width: 100%;\n}\n.amos-js-form-skeleton-label {\n flex-shrink: 0;\n font-size: var(--label-font-size);\n height: 1.75rem;\n line-height: 1.75rem;\n}\n.amos-js-form-skeleton-input {\n animation: amos-js-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n background: var(--accent);\n border-radius: calc(var(--radius) * 0.8);\n box-sizing: border-box;\n height: var(--input-height);\n width: 100%;\n}\n.amos-js-form-skeleton-input-floating {\n height: var(--floating-input-height);\n}\n.amos-js-form-skeleton-row {\n align-items: flex-start;\n display: flex;\n gap: var(--control-gap);\n width: 100%;\n}\n.amos-js-form-skeleton-row-stack {\n display: flex;\n flex-direction: column;\n gap: var(--field-gap);\n width: 100%;\n}\n@container (min-width: 24rem) {\n .amos-js-form-skeleton-row-stack {\n align-items: flex-start;\n flex-direction: row;\n gap: var(--control-gap);\n }\n}\n@keyframes amos-js-skeleton-pulse {\n 50% { opacity: 0.5; }\n}\n@media (prefers-reduced-motion: reduce) {\n .amos-js-form-skeleton-input {\n animation: none;\n }\n}\n";
405
+ function M() {
406
+ if (document.getElementById(k)) return;
407
+ let e = document.createElement("style");
408
+ e.id = k, e.textContent = j, document.head.appendChild(e);
409
+ }
410
+ function N(e, t) {
411
+ for (let [t, n] of Object.entries(A)) e.style.setProperty(t, n);
412
+ let n = t?.themeVariables;
413
+ if (n) for (let [t, r] of Object.entries(n)) typeof r == "string" && r.trim() !== "" && e.style.setProperty(t, r.trim());
414
+ }
415
+ function P(e, t) {
416
+ let n = document.createElement("div");
417
+ if (n.className = e, t) for (let e of t) n.appendChild(e);
418
+ return n;
419
+ }
420
+ function F(e, t) {
421
+ let n = P("amos-js-form-skeleton-field");
422
+ t !== void 0 && (n.style.flexGrow = String(t)), e === "above" && n.appendChild(P("amos-js-form-skeleton-label"));
423
+ let r = P("amos-js-form-skeleton-input");
424
+ return e === "floating" && r.classList.add("amos-js-form-skeleton-input-floating"), n.appendChild(r), n;
425
+ }
426
+ function I(e, t) {
427
+ return P(t ? "amos-js-form-skeleton-row-stack" : "amos-js-form-skeleton-row", e);
428
+ }
429
+ function L({ labels: e, requirement: t, wrapCountryZip: n }) {
430
+ return t === "full" ? [
431
+ F(e),
432
+ F(e),
433
+ I([
434
+ F(e, 1.4),
435
+ F(e, .7),
436
+ F(e, .8)
437
+ ], !1),
438
+ F(e)
439
+ ] : [I([F(e), F(e)], n)];
440
+ }
441
+ function R(e) {
442
+ let t = e.appearance?.labels ?? "above", n = e.billingAddressRequirement ?? "country";
443
+ if (e.kind === "card") {
444
+ let r = [F(t), I([F(t), F(t)], !1)];
445
+ return e.additionalFields?.cardholderName && r.push(F(t)), r.push(...L({
446
+ labels: t,
447
+ requirement: n,
448
+ wrapCountryZip: !1
449
+ })), r;
450
+ }
451
+ return [
452
+ F(t),
453
+ I([F(t), F(t)], !0),
454
+ F(t),
455
+ I([F("above"), F("above")], !0),
456
+ ...L({
457
+ labels: t,
458
+ requirement: n,
459
+ wrapCountryZip: !0
460
+ })
461
+ ];
462
+ }
463
+ function z(e) {
464
+ M();
465
+ let t = P("amos-js-form-skeleton");
466
+ t.setAttribute("aria-hidden", "true");
467
+ function n(e) {
468
+ N(t, e.appearance), t.replaceChildren(...R(e));
469
+ }
470
+ return n(e), {
471
+ element: t,
472
+ update: n
473
+ };
474
+ }
475
+ //#endregion
395
476
  //#region src/payment-method-form.ts
396
- var k = {
477
+ var B = {
397
478
  country: 212,
398
479
  full: 452
399
- }, A = 80, j = {
480
+ }, V = 80, H = {
400
481
  country: 400,
401
482
  full: 640
402
483
  };
403
- function M(e, t = { cardholderName: !1 }, n = "country") {
484
+ function U(e, t = { cardholderName: !1 }, n = "country") {
404
485
  let r = Object.entries(t).filter(([, e]) => e).map(([e]) => e).join(","), a = new URLSearchParams({
405
486
  token: e,
406
487
  additionalFields: r,
@@ -408,20 +489,20 @@ function M(e, t = { cardholderName: !1 }, n = "country") {
408
489
  });
409
490
  return `${i(e)}/iframe/card?${a}`;
410
491
  }
411
- function N(e, t = "country") {
492
+ function W(e, t = "country") {
412
493
  let n = new URLSearchParams({
413
494
  token: e,
414
495
  billingAddressRequirement: t
415
496
  });
416
497
  return `${i(e)}/iframe/bank?${n}`;
417
498
  }
418
- function P(e = { cardholderName: !1 }, t = "country") {
419
- return `${(k[t] ?? k.country) + (e.cardholderName ? A : 0)}px`;
499
+ function G(e = { cardholderName: !1 }, t = "country") {
500
+ return `${(B[t] ?? B.country) + (e.cardholderName ? V : 0)}px`;
420
501
  }
421
- function F(e = "country") {
422
- return `${j[e] ?? j.country}px`;
502
+ function K(e = "country") {
503
+ return `${H[e] ?? H.country}px`;
423
504
  }
424
- function I(e, t) {
505
+ function q(e, t) {
425
506
  let n = { ...t };
426
507
  function r(t) {
427
508
  if (t.source === e.contentWindow) switch (t.data.type) {
@@ -467,7 +548,7 @@ function I(e, t) {
467
548
  }
468
549
  //#endregion
469
550
  //#region src/mount.ts
470
- function L(e) {
551
+ function J(e) {
471
552
  if (typeof e == "string") {
472
553
  let t = document.querySelector(e);
473
554
  if (!(t instanceof HTMLElement)) throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);
@@ -475,74 +556,126 @@ function L(e) {
475
556
  }
476
557
  return e;
477
558
  }
478
- var R = {
559
+ var Y = {
479
560
  width: "calc(100% + 8px)",
480
561
  transition: "opacity 150ms ease-in, height 200ms ease-in-out",
481
562
  margin: "0 -4px",
482
563
  opacity: "0",
483
564
  border: "0"
565
+ }, X = {
566
+ width: "100%",
567
+ transition: "height 200ms ease-in-out",
568
+ margin: "0",
569
+ opacity: "0",
570
+ border: "0"
571
+ }, Z = {
572
+ position: "absolute",
573
+ top: "0",
574
+ left: "-4px",
575
+ width: "calc(100% + 8px)",
576
+ height: "100%",
577
+ margin: "0",
578
+ transition: "none",
579
+ pointerEvents: "none"
484
580
  };
485
- function z({ src: e, title: t, name: n, height: r, allow: i }) {
486
- let a = document.createElement("iframe");
487
- return a.src = e, a.title = t, a.name = n, a.setAttribute("role", "presentation"), a.scrolling = "no", i && (a.allow = i), Object.assign(a.style, R, { height: r }), a;
488
- }
489
- function B(e, t) {
490
- let n = L(e), { renderToken: r, additionalFields: i = { cardholderName: !1 }, billingAddressRequirement: a = "country", ...o } = t, s = z({
491
- src: M(r, i, a),
492
- title: "Secure credit card payment method form powered by Amos",
493
- name: "amos-credit-card-payment-method-form",
494
- height: P(i, a)
495
- });
496
- n.appendChild(s);
497
- let c = I(s, {
498
- ...o,
581
+ function Q({ src: e, title: t, name: n, height: r, allow: i, style: a = Y }) {
582
+ let o = document.createElement("iframe");
583
+ return o.src = e, o.title = t, o.name = n, o.setAttribute("role", "presentation"), o.scrolling = "no", i && (o.allow = i), Object.assign(o.style, a, { height: r }), o;
584
+ }
585
+ function $({ host: e, iframe: t, listenerOptions: n, skeletonOptions: r }) {
586
+ let i = document.createElement("div");
587
+ i.style.position = "relative", i.style.width = "100%", i.setAttribute("aria-busy", "true");
588
+ let a = z(r);
589
+ Object.assign(t.style, Z), i.append(a.element, t), e.appendChild(i);
590
+ let o = !1, s = !1, c, l = r.appearance, u, d;
591
+ function f() {
592
+ return i.getBoundingClientRect().height;
593
+ }
594
+ function p() {
595
+ return c ? Number.parseFloat(c) : NaN;
596
+ }
597
+ function m() {
598
+ if (o) return;
599
+ o = !0, d !== void 0 && (clearTimeout(d), d = void 0);
600
+ let e = f(), n = p(), r = Number.isFinite(n) ? Math.max(n, e) : e;
601
+ t.style.transition = "none", t.style.position = "", t.style.top = "", t.style.left = "", t.style.margin = Y.margin ?? "", t.style.height = `${r}px`, t.style.opacity = "1", t.style.pointerEvents = "", a.element.remove(), i.removeAttribute("aria-busy"), u = setTimeout(() => {
602
+ t.style.transition = "height 200ms ease-in-out";
603
+ }, 400);
604
+ }
605
+ function h() {
606
+ if (o || !s) return;
607
+ let e = p(), t = f();
608
+ Number.isFinite(e) && e >= t - 2 && m();
609
+ }
610
+ let g = q(t, {
611
+ ...n,
499
612
  onHeightChange: (e) => {
500
- s.style.height = e, o.onHeightChange?.(e);
613
+ c = e, o ? t.style.height = e : h(), n.onHeightChange?.(e);
501
614
  },
502
615
  onAppearanceReady: () => {
503
- s.style.opacity = "1", o.onAppearanceReady?.();
616
+ s = !0, h(), !o && d === void 0 && (d = setTimeout(() => {
617
+ m();
618
+ }, 1500)), n.onAppearanceReady?.();
504
619
  }
505
620
  });
506
621
  return {
507
- iframe: s,
508
- update: c.update,
622
+ iframe: t,
623
+ update(e) {
624
+ g.update(e), !o && "appearance" in e && (l = e.appearance, a.update({
625
+ ...r,
626
+ appearance: l
627
+ }));
628
+ },
509
629
  destroy() {
510
- c.destroy(), s.remove();
630
+ u !== void 0 && clearTimeout(u), d !== void 0 && clearTimeout(d), g.destroy(), i.remove();
511
631
  }
512
632
  };
513
633
  }
514
- function V(e, t) {
515
- let n = L(e), { renderToken: r, billingAddressRequirement: i = "country", ...a } = t, o = z({
516
- src: N(r, i),
517
- title: "Secure bank account payment method form powered by Amos",
518
- name: "amos-bank-account-payment-method-form",
519
- height: F(i)
520
- });
521
- n.appendChild(o);
522
- let s = I(o, {
523
- ...a,
524
- onHeightChange: (e) => {
525
- o.style.height = e, a.onHeightChange?.(e);
526
- },
527
- onAppearanceReady: () => {
528
- o.style.opacity = "1", a.onAppearanceReady?.();
634
+ function ee(e, t) {
635
+ let n = J(e), { renderToken: r, additionalFields: i = { cardholderName: !1 }, billingAddressRequirement: a = "country", ...o } = t;
636
+ return $({
637
+ host: n,
638
+ iframe: Q({
639
+ src: U(r, i, a),
640
+ title: "Secure credit card payment method form powered by Amos",
641
+ name: "amos-credit-card-payment-method-form",
642
+ height: G(i, a)
643
+ }),
644
+ listenerOptions: o,
645
+ skeletonOptions: {
646
+ kind: "card",
647
+ appearance: o.appearance,
648
+ additionalFields: i,
649
+ billingAddressRequirement: a
529
650
  }
530
651
  });
531
- return {
532
- iframe: o,
533
- update: s.update,
534
- destroy() {
535
- s.destroy(), o.remove();
652
+ }
653
+ function te(e, t) {
654
+ let n = J(e), { renderToken: r, billingAddressRequirement: i = "country", ...a } = t;
655
+ return $({
656
+ host: n,
657
+ iframe: Q({
658
+ src: W(r, i),
659
+ title: "Secure bank account payment method form powered by Amos",
660
+ name: "amos-bank-account-payment-method-form",
661
+ height: K(i)
662
+ }),
663
+ listenerOptions: a,
664
+ skeletonOptions: {
665
+ kind: "bank",
666
+ appearance: a.appearance,
667
+ billingAddressRequirement: i
536
668
  }
537
- };
669
+ });
538
670
  }
539
- function H(e, t) {
540
- let n = L(e), { renderToken: r, ...i } = t, a = z({
671
+ function ne(e, t) {
672
+ let n = J(e), { renderToken: r, ...i } = t, a = Q({
541
673
  src: T(r),
542
674
  title: "Secure Google Pay button powered by Amos",
543
675
  name: "amos-google-pay-button",
544
676
  height: E(),
545
- allow: "payment"
677
+ allow: "payment",
678
+ style: X
546
679
  });
547
680
  n.appendChild(a);
548
681
  let o = D(a, {
@@ -562,13 +695,14 @@ function H(e, t) {
562
695
  }
563
696
  };
564
697
  }
565
- function U(e, t) {
566
- let n = L(e), { renderToken: r, ...i } = t, a = z({
698
+ function re(e, t) {
699
+ let n = J(e), { renderToken: r, ...i } = t, a = Q({
567
700
  src: x(r),
568
701
  title: "Secure Apple Pay button powered by Amos",
569
702
  name: "amos-apple-pay-button",
570
703
  height: S(),
571
- allow: "payment"
704
+ allow: "payment",
705
+ style: X
572
706
  });
573
707
  n.appendChild(a);
574
708
  let o = w(a, {
@@ -589,4 +723,4 @@ function U(e, t) {
589
723
  };
590
724
  }
591
725
  //#endregion
592
- export { w as attachApplePayButtonListeners, D as attachGooglePayButtonListeners, I as attachPaymentMethodFormListeners, v as confirmPaymentIntent, y as confirmSetupIntent, c as createMessage, r as decodeJwt, O as formatGooglePayPaymentData, S as getApplePayButtonInitialHeight, x as getApplePayButtonSrc, F as getBankAccountFormInitialHeight, N as getBankAccountFormSrc, P as getCreditCardFormInitialHeight, M as getCreditCardFormSrc, i as getEmbedOrigin, E as getGooglePayButtonInitialHeight, T as getGooglePayButtonSrc, U as mountAmosApplePayButton, V as mountAmosBankAccountPaymentMethodForm, B as mountAmosCreditCardPaymentMethodForm, H as mountAmosGooglePayButton, o as pickApplePayButtonElementProps, s as pickGooglePayButtonElementProps, _ as resetForm, b as sendConfirmationResult, u as sendParentReadyMessage, a as serializeWalletButtonStyle, m as updateAmount, d as updateAppearance, f as updateApplePayButton, p as updateGooglePayButton, h as updateMerchantName, g as validateForm };
726
+ export { w as attachApplePayButtonListeners, D as attachGooglePayButtonListeners, q as attachPaymentMethodFormListeners, v as confirmPaymentIntent, y as confirmSetupIntent, c as createMessage, r as decodeJwt, O as formatGooglePayPaymentData, S as getApplePayButtonInitialHeight, x as getApplePayButtonSrc, K as getBankAccountFormInitialHeight, W as getBankAccountFormSrc, G as getCreditCardFormInitialHeight, U as getCreditCardFormSrc, i as getEmbedOrigin, E as getGooglePayButtonInitialHeight, T as getGooglePayButtonSrc, re as mountAmosApplePayButton, te as mountAmosBankAccountPaymentMethodForm, ee as mountAmosCreditCardPaymentMethodForm, ne as mountAmosGooglePayButton, o as pickApplePayButtonElementProps, s as pickGooglePayButtonElementProps, _ as resetForm, b as sendConfirmationResult, u as sendParentReadyMessage, a as serializeWalletButtonStyle, m as updateAmount, d as updateAppearance, f as updateApplePayButton, p as updateGooglePayButton, h as updateMerchantName, g as validateForm };
package/dist/mount.d.ts CHANGED
@@ -45,6 +45,9 @@ export type AmosPaymentMethodFormMountController = PaymentMethodFormController &
45
45
  * element. Returns a controller exposing the underlying iframe, an
46
46
  * `update()` method, and a `destroy()` method.
47
47
  *
48
+ * A field-shaped skeleton is shown immediately and replaced by the
49
+ * iframe once appearance is applied.
50
+ *
48
51
  * Use the returned `controller.iframe` when calling
49
52
  * {@link validateForm}, {@link confirmPaymentIntent}, or
50
53
  * {@link confirmSetupIntent}.
@@ -73,6 +76,9 @@ export type AmosBankAccountPaymentMethodFormOptions = PaymentMethodFormListenerO
73
76
  * element. Returns a controller exposing the underlying iframe, an
74
77
  * `update()` method, and a `destroy()` method.
75
78
  *
79
+ * A field-shaped skeleton is shown immediately and replaced by the
80
+ * iframe once appearance is applied.
81
+ *
76
82
  * Use the returned `controller.iframe` when calling
77
83
  * {@link validateForm}, {@link confirmPaymentIntent}, or
78
84
  * {@link confirmSetupIntent}.
package/dist/types.d.ts CHANGED
@@ -106,6 +106,13 @@ export type ApplePayButtonType = "plain" | "buy" | "set-up" | "donate" | "check-
106
106
  * @see https://developer.apple.com/documentation/apple_pay_on_the_web/apple-pay-button
107
107
  */
108
108
  export type ApplePayButtonElementProps = {
109
+ /**
110
+ * Stretch the button to the iframe width while preserving its default
111
+ * compact size when omitted.
112
+ *
113
+ * @default false
114
+ */
115
+ fullWidth?: boolean;
109
116
  /**
110
117
  * Apple Pay button color.
111
118
  *
@@ -138,6 +145,13 @@ export type ApplePayButtonElementProps = {
138
145
  * @see https://developers.google.com/pay/api/web/guides/resources/customize
139
146
  */
140
147
  export type GooglePayButtonElementProps = {
148
+ /**
149
+ * Stretch the button to the iframe width while preserving its default
150
+ * compact size when omitted.
151
+ *
152
+ * @default false
153
+ */
154
+ fullWidth?: boolean;
141
155
  /**
142
156
  * Button label / verb.
143
157
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amos.com/amos-js",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",
package/src/apple-pay.ts CHANGED
@@ -209,6 +209,7 @@ export function attachApplePayButtonListeners(
209
209
  const hadAmount = "amount" in patch;
210
210
  const hadMerchantName = "merchantName" in patch;
211
211
  const hadButtonProps =
212
+ "fullWidth" in patch ||
212
213
  "buttonstyle" in patch ||
213
214
  "type" in patch ||
214
215
  "locale" in patch ||
@@ -0,0 +1,244 @@
1
+ import type {
2
+ BillingAddressRequirement,
3
+ CreditCardAdditionalFields,
4
+ } from "./payment-method-form";
5
+ import type { Appearance, AppearanceLabels, ThemeVariable } from "./types";
6
+
7
+ const STYLE_ID = "amos-js-form-skeleton-styles";
8
+
9
+ const SKELETON_THEME_DEFAULTS: Record<string, string> = {
10
+ "--accent": "oklch(0.97 0 0)",
11
+ "--radius": "0.625rem",
12
+ "--input-height": "2.25rem",
13
+ "--floating-input-height": "3.25rem",
14
+ "--field-gap": "1rem",
15
+ "--control-gap": "0.5rem",
16
+ "--label-font-size": "0.875rem",
17
+ };
18
+
19
+ const SKELETON_STYLES = `
20
+ .amos-js-form-skeleton {
21
+ box-sizing: border-box;
22
+ container-type: inline-size;
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--field-gap);
26
+ margin: 0 -4px;
27
+ padding-block: 0.25rem;
28
+ pointer-events: none;
29
+ width: calc(100% + 8px);
30
+ }
31
+ .amos-js-form-skeleton-field {
32
+ display: flex;
33
+ flex: 1 1 0;
34
+ flex-direction: column;
35
+ min-width: 0;
36
+ width: 100%;
37
+ }
38
+ .amos-js-form-skeleton-label {
39
+ flex-shrink: 0;
40
+ font-size: var(--label-font-size);
41
+ height: 1.75rem;
42
+ line-height: 1.75rem;
43
+ }
44
+ .amos-js-form-skeleton-input {
45
+ animation: amos-js-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
46
+ background: var(--accent);
47
+ border-radius: calc(var(--radius) * 0.8);
48
+ box-sizing: border-box;
49
+ height: var(--input-height);
50
+ width: 100%;
51
+ }
52
+ .amos-js-form-skeleton-input-floating {
53
+ height: var(--floating-input-height);
54
+ }
55
+ .amos-js-form-skeleton-row {
56
+ align-items: flex-start;
57
+ display: flex;
58
+ gap: var(--control-gap);
59
+ width: 100%;
60
+ }
61
+ .amos-js-form-skeleton-row-stack {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: var(--field-gap);
65
+ width: 100%;
66
+ }
67
+ @container (min-width: 24rem) {
68
+ .amos-js-form-skeleton-row-stack {
69
+ align-items: flex-start;
70
+ flex-direction: row;
71
+ gap: var(--control-gap);
72
+ }
73
+ }
74
+ @keyframes amos-js-skeleton-pulse {
75
+ 50% { opacity: 0.5; }
76
+ }
77
+ @media (prefers-reduced-motion: reduce) {
78
+ .amos-js-form-skeleton-input {
79
+ animation: none;
80
+ }
81
+ }
82
+ `;
83
+
84
+ function ensureSkeletonStyles(): void {
85
+ if (document.getElementById(STYLE_ID)) {
86
+ return;
87
+ }
88
+ const style = document.createElement("style");
89
+ style.id = STYLE_ID;
90
+ style.textContent = SKELETON_STYLES;
91
+ document.head.appendChild(style);
92
+ }
93
+
94
+ function applyTheme(
95
+ element: HTMLElement,
96
+ appearance: Appearance | undefined,
97
+ ): void {
98
+ for (const [property, value] of Object.entries(SKELETON_THEME_DEFAULTS)) {
99
+ element.style.setProperty(property, value);
100
+ }
101
+ const themeVariables = appearance?.themeVariables;
102
+ if (!themeVariables) {
103
+ return;
104
+ }
105
+ for (const [property, value] of Object.entries(themeVariables) as Array<
106
+ [ThemeVariable, string | undefined]
107
+ >) {
108
+ if (typeof value === "string" && value.trim() !== "") {
109
+ element.style.setProperty(property, value.trim());
110
+ }
111
+ }
112
+ }
113
+
114
+ function div(className: string, children?: Array<HTMLElement>): HTMLDivElement {
115
+ const node = document.createElement("div");
116
+ node.className = className;
117
+ if (children) {
118
+ for (const child of children) {
119
+ node.appendChild(child);
120
+ }
121
+ }
122
+ return node;
123
+ }
124
+
125
+ function field(labels: AppearanceLabels, grow?: number): HTMLDivElement {
126
+ const wrap = div("amos-js-form-skeleton-field");
127
+ if (grow !== undefined) {
128
+ wrap.style.flexGrow = String(grow);
129
+ }
130
+ if (labels === "above") {
131
+ wrap.appendChild(div("amos-js-form-skeleton-label"));
132
+ }
133
+ const input = div("amos-js-form-skeleton-input");
134
+ if (labels === "floating") {
135
+ input.classList.add("amos-js-form-skeleton-input-floating");
136
+ }
137
+ wrap.appendChild(input);
138
+ return wrap;
139
+ }
140
+
141
+ function row(children: Array<HTMLElement>, wrapAtSm: boolean): HTMLDivElement {
142
+ return div(
143
+ wrapAtSm ? "amos-js-form-skeleton-row-stack" : "amos-js-form-skeleton-row",
144
+ children,
145
+ );
146
+ }
147
+
148
+ function billingFields({
149
+ labels,
150
+ requirement,
151
+ wrapCountryZip,
152
+ }: {
153
+ labels: AppearanceLabels;
154
+ requirement: BillingAddressRequirement;
155
+ wrapCountryZip: boolean;
156
+ }): Array<HTMLElement> {
157
+ if (requirement === "full") {
158
+ return [
159
+ field(labels),
160
+ field(labels),
161
+ row([field(labels, 1.4), field(labels, 0.7), field(labels, 0.8)], false),
162
+ field(labels),
163
+ ];
164
+ }
165
+ return [row([field(labels), field(labels)], wrapCountryZip)];
166
+ }
167
+
168
+ export type PaymentMethodFormSkeletonKind = "card" | "bank";
169
+
170
+ export type PaymentMethodFormSkeletonOptions = {
171
+ kind: PaymentMethodFormSkeletonKind;
172
+ appearance?: Appearance;
173
+ additionalFields?: CreditCardAdditionalFields;
174
+ billingAddressRequirement?: BillingAddressRequirement;
175
+ };
176
+
177
+ function buildChildren(
178
+ options: PaymentMethodFormSkeletonOptions,
179
+ ): Array<HTMLElement> {
180
+ const labels = options.appearance?.labels ?? "above";
181
+ const billingAddressRequirement =
182
+ options.billingAddressRequirement ?? "country";
183
+
184
+ if (options.kind === "card") {
185
+ const children: Array<HTMLElement> = [
186
+ field(labels),
187
+ row([field(labels), field(labels)], false),
188
+ ];
189
+ if (options.additionalFields?.cardholderName) {
190
+ children.push(field(labels));
191
+ }
192
+ children.push(
193
+ ...billingFields({
194
+ labels,
195
+ requirement: billingAddressRequirement,
196
+ wrapCountryZip: false,
197
+ }),
198
+ );
199
+ return children;
200
+ }
201
+
202
+ return [
203
+ field(labels),
204
+ row([field(labels), field(labels)], true),
205
+ field(labels),
206
+ row([field("above"), field("above")], true),
207
+ ...billingFields({
208
+ labels,
209
+ requirement: billingAddressRequirement,
210
+ wrapCountryZip: true,
211
+ }),
212
+ ];
213
+ }
214
+
215
+ export type PaymentMethodFormSkeleton = {
216
+ element: HTMLElement;
217
+ update: (options: PaymentMethodFormSkeletonOptions) => void;
218
+ };
219
+
220
+ /**
221
+ * Host-page placeholder that mirrors card/bank field layout using the
222
+ * same appearance variables the iframe will apply. Shown immediately
223
+ * while the iframe document loads, then removed when appearance is
224
+ * ready.
225
+ */
226
+ export function createPaymentMethodFormSkeleton(
227
+ options: PaymentMethodFormSkeletonOptions,
228
+ ): PaymentMethodFormSkeleton {
229
+ ensureSkeletonStyles();
230
+ const element = div("amos-js-form-skeleton");
231
+ element.setAttribute("aria-hidden", "true");
232
+
233
+ function render(next: PaymentMethodFormSkeletonOptions): void {
234
+ applyTheme(element, next.appearance);
235
+ element.replaceChildren(...buildChildren(next));
236
+ }
237
+
238
+ render(options);
239
+
240
+ return {
241
+ element,
242
+ update: render,
243
+ };
244
+ }
package/src/google-pay.ts CHANGED
@@ -178,6 +178,7 @@ export function attachGooglePayButtonListeners(
178
178
  const hadAmount = "amount" in patch;
179
179
  const hadMerchantName = "merchantName" in patch;
180
180
  const hadButtonProps =
181
+ "fullWidth" in patch ||
181
182
  "buttonType" in patch ||
182
183
  "buttonColor" in patch ||
183
184
  "buttonRadius" in patch ||
package/src/mount.ts CHANGED
@@ -5,6 +5,10 @@ import {
5
5
  getApplePayButtonInitialHeight,
6
6
  getApplePayButtonSrc,
7
7
  } from "./apple-pay";
8
+ import {
9
+ createPaymentMethodFormSkeleton,
10
+ type PaymentMethodFormSkeletonOptions,
11
+ } from "./form-skeleton";
8
12
  import {
9
13
  attachGooglePayButtonListeners,
10
14
  type GooglePayButtonController,
@@ -39,7 +43,7 @@ function resolveContainer(container: Container): HTMLElement {
39
43
  return container;
40
44
  }
41
45
 
42
- const SHARED_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
46
+ const FORM_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
43
47
  width: "calc(100% + 8px)",
44
48
  transition: "opacity 150ms ease-in, height 200ms ease-in-out",
45
49
  margin: "0 -4px",
@@ -47,18 +51,40 @@ const SHARED_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
47
51
  border: "0",
48
52
  };
49
53
 
54
+ const WALLET_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
55
+ width: "100%",
56
+ transition: "height 200ms ease-in-out",
57
+ margin: "0",
58
+ opacity: "0",
59
+ border: "0",
60
+ };
61
+
62
+ const SKELETON_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
63
+ position: "absolute",
64
+ top: "0",
65
+ left: "-4px",
66
+ width: "calc(100% + 8px)",
67
+ height: "100%",
68
+ margin: "0",
69
+ // Do not interpolate opacity while the skeleton is showing.
70
+ transition: "none",
71
+ pointerEvents: "none",
72
+ };
73
+
50
74
  function createIframe({
51
75
  src,
52
76
  title,
53
77
  name,
54
78
  height,
55
79
  allow,
80
+ style = FORM_IFRAME_STYLE,
56
81
  }: {
57
82
  src: string;
58
83
  title: string;
59
84
  name: string;
60
85
  height: string;
61
86
  allow?: string;
87
+ style?: Partial<CSSStyleDeclaration>;
62
88
  }): HTMLIFrameElement {
63
89
  const iframe = document.createElement("iframe");
64
90
  iframe.src = src;
@@ -69,10 +95,137 @@ function createIframe({
69
95
  if (allow) {
70
96
  iframe.allow = allow;
71
97
  }
72
- Object.assign(iframe.style, SHARED_IFRAME_STYLE, { height });
98
+ Object.assign(iframe.style, style, { height });
73
99
  return iframe;
74
100
  }
75
101
 
102
+ function mountPaymentMethodFormWithSkeleton({
103
+ host,
104
+ iframe,
105
+ listenerOptions,
106
+ skeletonOptions,
107
+ }: {
108
+ host: HTMLElement;
109
+ iframe: HTMLIFrameElement;
110
+ listenerOptions: PaymentMethodFormListenerOptions;
111
+ skeletonOptions: PaymentMethodFormSkeletonOptions;
112
+ }): AmosPaymentMethodFormMountController {
113
+ const wrapper = document.createElement("div");
114
+ wrapper.style.position = "relative";
115
+ wrapper.style.width = "100%";
116
+ wrapper.setAttribute("aria-busy", "true");
117
+
118
+ const skeleton = createPaymentMethodFormSkeleton(skeletonOptions);
119
+ Object.assign(iframe.style, SKELETON_IFRAME_STYLE);
120
+
121
+ wrapper.append(skeleton.element, iframe);
122
+ host.appendChild(wrapper);
123
+
124
+ let revealed = false;
125
+ let appearanceReady = false;
126
+ let lastHeight: string | undefined;
127
+ let appearance = skeletonOptions.appearance;
128
+ let heightTransitionTimer: ReturnType<typeof setTimeout> | undefined;
129
+ let fallbackRevealTimer: ReturnType<typeof setTimeout> | undefined;
130
+
131
+ function skeletonHeightPx(): number {
132
+ return wrapper.getBoundingClientRect().height;
133
+ }
134
+
135
+ function reportedHeightPx(): number {
136
+ return lastHeight ? Number.parseFloat(lastHeight) : Number.NaN;
137
+ }
138
+
139
+ function reveal(): void {
140
+ if (revealed) {
141
+ return;
142
+ }
143
+ revealed = true;
144
+ if (fallbackRevealTimer !== undefined) {
145
+ clearTimeout(fallbackRevealTimer);
146
+ fallbackRevealTimer = undefined;
147
+ }
148
+
149
+ const skeletonPx = skeletonHeightPx();
150
+ const reportedPx = reportedHeightPx();
151
+ const revealPx = Number.isFinite(reportedPx)
152
+ ? Math.max(reportedPx, skeletonPx)
153
+ : skeletonPx;
154
+
155
+ iframe.style.transition = "none";
156
+ iframe.style.position = "";
157
+ iframe.style.top = "";
158
+ iframe.style.left = "";
159
+ iframe.style.margin = FORM_IFRAME_STYLE.margin ?? "";
160
+ iframe.style.height = `${revealPx}px`;
161
+ iframe.style.opacity = "1";
162
+ iframe.style.pointerEvents = "";
163
+ skeleton.element.remove();
164
+ wrapper.removeAttribute("aria-busy");
165
+
166
+ // Height easing is for later layout changes (ZIP show/hide, errors),
167
+ // not the first paint — that would animate the last row into place.
168
+ heightTransitionTimer = setTimeout(() => {
169
+ iframe.style.transition = "height 200ms ease-in-out";
170
+ }, 400);
171
+ }
172
+
173
+ function tryReveal(): void {
174
+ if (revealed || !appearanceReady) {
175
+ return;
176
+ }
177
+ const reportedPx = reportedHeightPx();
178
+ const skeletonPx = skeletonHeightPx();
179
+ if (Number.isFinite(reportedPx) && reportedPx >= skeletonPx - 2) {
180
+ reveal();
181
+ }
182
+ }
183
+
184
+ const controller = attachPaymentMethodFormListeners(iframe, {
185
+ ...listenerOptions,
186
+ onHeightChange: (height) => {
187
+ lastHeight = height;
188
+ if (revealed) {
189
+ iframe.style.height = height;
190
+ } else {
191
+ tryReveal();
192
+ }
193
+ listenerOptions.onHeightChange?.(height);
194
+ },
195
+ onAppearanceReady: () => {
196
+ appearanceReady = true;
197
+ tryReveal();
198
+ if (!revealed && fallbackRevealTimer === undefined) {
199
+ fallbackRevealTimer = setTimeout(() => {
200
+ reveal();
201
+ }, 1500);
202
+ }
203
+ listenerOptions.onAppearanceReady?.();
204
+ },
205
+ });
206
+
207
+ return {
208
+ iframe,
209
+ update(patch) {
210
+ controller.update(patch);
211
+ if (!revealed && "appearance" in patch) {
212
+ appearance = patch.appearance;
213
+ skeleton.update({ ...skeletonOptions, appearance });
214
+ }
215
+ },
216
+ destroy() {
217
+ if (heightTransitionTimer !== undefined) {
218
+ clearTimeout(heightTransitionTimer);
219
+ }
220
+ if (fallbackRevealTimer !== undefined) {
221
+ clearTimeout(fallbackRevealTimer);
222
+ }
223
+ controller.destroy();
224
+ wrapper.remove();
225
+ },
226
+ };
227
+ }
228
+
76
229
  /**
77
230
  * Options accepted by {@link mountAmosCreditCardPaymentMethodForm}.
78
231
  */
@@ -120,6 +273,9 @@ export type AmosPaymentMethodFormMountController =
120
273
  * element. Returns a controller exposing the underlying iframe, an
121
274
  * `update()` method, and a `destroy()` method.
122
275
  *
276
+ * A field-shaped skeleton is shown immediately and replaced by the
277
+ * iframe once appearance is applied.
278
+ *
123
279
  * Use the returned `controller.iframe` when calling
124
280
  * {@link validateForm}, {@link confirmPaymentIntent}, or
125
281
  * {@link confirmSetupIntent}.
@@ -149,28 +305,18 @@ export function mountAmosCreditCardPaymentMethodForm(
149
305
  billingAddressRequirement,
150
306
  ),
151
307
  });
152
- host.appendChild(iframe);
153
308
 
154
- const controller = attachPaymentMethodFormListeners(iframe, {
155
- ...listenerOptions,
156
- onHeightChange: (height) => {
157
- iframe.style.height = height;
158
- listenerOptions.onHeightChange?.(height);
159
- },
160
- onAppearanceReady: () => {
161
- iframe.style.opacity = "1";
162
- listenerOptions.onAppearanceReady?.();
163
- },
164
- });
165
-
166
- return {
309
+ return mountPaymentMethodFormWithSkeleton({
310
+ host,
167
311
  iframe,
168
- update: controller.update,
169
- destroy() {
170
- controller.destroy();
171
- iframe.remove();
312
+ listenerOptions,
313
+ skeletonOptions: {
314
+ kind: "card",
315
+ appearance: listenerOptions.appearance,
316
+ additionalFields,
317
+ billingAddressRequirement,
172
318
  },
173
- };
319
+ });
174
320
  }
175
321
 
176
322
  /**
@@ -198,6 +344,9 @@ export type AmosBankAccountPaymentMethodFormOptions =
198
344
  * element. Returns a controller exposing the underlying iframe, an
199
345
  * `update()` method, and a `destroy()` method.
200
346
  *
347
+ * A field-shaped skeleton is shown immediately and replaced by the
348
+ * iframe once appearance is applied.
349
+ *
201
350
  * Use the returned `controller.iframe` when calling
202
351
  * {@link validateForm}, {@link confirmPaymentIntent}, or
203
352
  * {@link confirmSetupIntent}.
@@ -219,28 +368,17 @@ export function mountAmosBankAccountPaymentMethodForm(
219
368
  name: "amos-bank-account-payment-method-form",
220
369
  height: getBankAccountFormInitialHeight(billingAddressRequirement),
221
370
  });
222
- host.appendChild(iframe);
223
371
 
224
- const controller = attachPaymentMethodFormListeners(iframe, {
225
- ...listenerOptions,
226
- onHeightChange: (height) => {
227
- iframe.style.height = height;
228
- listenerOptions.onHeightChange?.(height);
229
- },
230
- onAppearanceReady: () => {
231
- iframe.style.opacity = "1";
232
- listenerOptions.onAppearanceReady?.();
233
- },
234
- });
235
-
236
- return {
372
+ return mountPaymentMethodFormWithSkeleton({
373
+ host,
237
374
  iframe,
238
- update: controller.update,
239
- destroy() {
240
- controller.destroy();
241
- iframe.remove();
375
+ listenerOptions,
376
+ skeletonOptions: {
377
+ kind: "bank",
378
+ appearance: listenerOptions.appearance,
379
+ billingAddressRequirement,
242
380
  },
243
- };
381
+ });
244
382
  }
245
383
 
246
384
  /**
@@ -284,6 +422,7 @@ export function mountAmosGooglePayButton(
284
422
  name: "amos-google-pay-button",
285
423
  height: getGooglePayButtonInitialHeight(),
286
424
  allow: "payment",
425
+ style: WALLET_IFRAME_STYLE,
287
426
  });
288
427
  host.appendChild(iframe);
289
428
 
@@ -350,6 +489,7 @@ export function mountAmosApplePayButton(
350
489
  name: "amos-apple-pay-button",
351
490
  height: getApplePayButtonInitialHeight(),
352
491
  allow: "payment",
492
+ style: WALLET_IFRAME_STYLE,
353
493
  });
354
494
  host.appendChild(iframe);
355
495
 
package/src/types.ts CHANGED
@@ -361,6 +361,13 @@ export type ApplePayButtonType =
361
361
  * @see https://developer.apple.com/documentation/apple_pay_on_the_web/apple-pay-button
362
362
  */
363
363
  export type ApplePayButtonElementProps = {
364
+ /**
365
+ * Stretch the button to the iframe width while preserving its default
366
+ * compact size when omitted.
367
+ *
368
+ * @default false
369
+ */
370
+ fullWidth?: boolean;
364
371
  /**
365
372
  * Apple Pay button color.
366
373
  *
@@ -394,6 +401,13 @@ export type ApplePayButtonElementProps = {
394
401
  * @see https://developers.google.com/pay/api/web/guides/resources/customize
395
402
  */
396
403
  export type GooglePayButtonElementProps = {
404
+ /**
405
+ * Stretch the button to the iframe width while preserving its default
406
+ * compact size when omitted.
407
+ *
408
+ * @default false
409
+ */
410
+ fullWidth?: boolean;
397
411
  /**
398
412
  * Button label / verb.
399
413
  *
@@ -452,6 +466,9 @@ export function pickApplePayButtonElementProps(
452
466
  options: ApplePayButtonElementProps,
453
467
  ): ApplePayButtonElementProps {
454
468
  const props: ApplePayButtonElementProps = {};
469
+ if (options.fullWidth !== undefined) {
470
+ props.fullWidth = options.fullWidth;
471
+ }
455
472
  if (options.buttonstyle !== undefined) {
456
473
  props.buttonstyle = options.buttonstyle;
457
474
  }
@@ -474,6 +491,9 @@ export function pickGooglePayButtonElementProps(
474
491
  options: GooglePayButtonElementProps,
475
492
  ): GooglePayButtonElementProps {
476
493
  const props: GooglePayButtonElementProps = {};
494
+ if (options.fullWidth !== undefined) {
495
+ props.fullWidth = options.fullWidth;
496
+ }
477
497
  if (options.buttonType !== undefined) {
478
498
  props.buttonType = options.buttonType;
479
499
  }