@amos.com/amos-js 0.9.15 → 0.9.17
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 +58 -42
- package/dist/apple-pay.d.ts +5 -1
- package/dist/form-skeleton.d.ts +31 -0
- package/dist/google-pay.d.ts +5 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -2
- package/dist/index.mjs +217 -160
- package/dist/messaging.d.ts +2 -1
- package/dist/mount.d.ts +6 -0
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
- package/src/apple-pay.ts +5 -1
- package/src/form-skeleton.ts +77 -2
- package/src/google-pay.ts +5 -1
- package/src/index.ts +14 -2
- package/src/messaging.ts +2 -1
- package/src/mount.ts +170 -40
- package/src/types.ts +4 -1
package/src/form-skeleton.ts
CHANGED
|
@@ -16,7 +16,7 @@ const SKELETON_THEME_DEFAULTS: Record<string, string> = {
|
|
|
16
16
|
"--label-font-size": "0.875rem",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const SKELETON_STYLES = `
|
|
19
|
+
export const SKELETON_STYLES = `
|
|
20
20
|
.amos-js-form-skeleton {
|
|
21
21
|
box-sizing: border-box;
|
|
22
22
|
container-type: inline-size;
|
|
@@ -71,6 +71,10 @@ const SKELETON_STYLES = `
|
|
|
71
71
|
gap: var(--control-gap);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
.amos-js-wallet-skeleton {
|
|
75
|
+
flex: none;
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
74
78
|
@keyframes amos-js-skeleton-pulse {
|
|
75
79
|
50% { opacity: 0.5; }
|
|
76
80
|
}
|
|
@@ -81,7 +85,7 @@ const SKELETON_STYLES = `
|
|
|
81
85
|
}
|
|
82
86
|
`;
|
|
83
87
|
|
|
84
|
-
function ensureSkeletonStyles(): void {
|
|
88
|
+
export function ensureSkeletonStyles(): void {
|
|
85
89
|
if (document.getElementById(STYLE_ID)) {
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
@@ -242,3 +246,74 @@ export function createPaymentMethodFormSkeleton(
|
|
|
242
246
|
update: render,
|
|
243
247
|
};
|
|
244
248
|
}
|
|
249
|
+
|
|
250
|
+
export type WalletButtonSkeletonOptions = {
|
|
251
|
+
/** Painted height of the wallet button slot (CSS length). */
|
|
252
|
+
height: string;
|
|
253
|
+
/** Corner radius matching the iframe / native button. */
|
|
254
|
+
borderRadius?: string;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export type WalletButtonSkeleton = {
|
|
258
|
+
element: HTMLElement;
|
|
259
|
+
update: (options: WalletButtonSkeletonOptions) => void;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Host-page placeholder for Google Pay / Apple Pay: a pulsing bar at
|
|
264
|
+
* the button's height. Shown immediately while the iframe loads, then
|
|
265
|
+
* removed when appearance is ready.
|
|
266
|
+
*/
|
|
267
|
+
export function createWalletButtonSkeleton(
|
|
268
|
+
options: WalletButtonSkeletonOptions,
|
|
269
|
+
): WalletButtonSkeleton {
|
|
270
|
+
ensureSkeletonStyles();
|
|
271
|
+
const element = div("amos-js-form-skeleton-input amos-js-wallet-skeleton");
|
|
272
|
+
element.setAttribute("aria-hidden", "true");
|
|
273
|
+
|
|
274
|
+
function render(next: WalletButtonSkeletonOptions): void {
|
|
275
|
+
applyTheme(element, undefined);
|
|
276
|
+
element.style.height = next.height;
|
|
277
|
+
element.style.borderRadius = next.borderRadius ?? "4px";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
render(options);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
element,
|
|
284
|
+
update: render,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function cssLength(value: unknown): string | undefined {
|
|
289
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
290
|
+
return `${value}px`;
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
293
|
+
return value.trim();
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Corner radius for a wallet-button skeleton. Prefers host iframe
|
|
300
|
+
* chrome, then native Google / Apple button radius, then 4px.
|
|
301
|
+
*/
|
|
302
|
+
export function resolveWalletButtonSkeletonBorderRadius({
|
|
303
|
+
iframeStyle,
|
|
304
|
+
buttonProps,
|
|
305
|
+
}: {
|
|
306
|
+
iframeStyle?: { borderRadius?: string | number };
|
|
307
|
+
buttonProps?: {
|
|
308
|
+
buttonRadius?: number;
|
|
309
|
+
style?: Record<string, string | number | undefined>;
|
|
310
|
+
};
|
|
311
|
+
}): string {
|
|
312
|
+
return (
|
|
313
|
+
cssLength(iframeStyle?.borderRadius) ??
|
|
314
|
+
cssLength(buttonProps?.buttonRadius) ??
|
|
315
|
+
cssLength(buttonProps?.style?.["borderRadius"]) ??
|
|
316
|
+
cssLength(buttonProps?.style?.["--apple-pay-button-border-radius"]) ??
|
|
317
|
+
"4px"
|
|
318
|
+
);
|
|
319
|
+
}
|
package/src/google-pay.ts
CHANGED
|
@@ -34,7 +34,11 @@ export function getGooglePayButtonInitialHeight(): string {
|
|
|
34
34
|
* Options accepted by {@link attachGooglePayButtonListeners}.
|
|
35
35
|
*/
|
|
36
36
|
export type GooglePayButtonListenerOptions = {
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Major-currency decimal string shown in the Google Pay sheet
|
|
39
|
+
* (e.g. `"50.00"` for $50.00). Converted to cents in
|
|
40
|
+
* `paymentIntentCreateAttributes.amount`.
|
|
41
|
+
*/
|
|
38
42
|
amount: string;
|
|
39
43
|
/** A user-visible merchant name. */
|
|
40
44
|
merchantName: string;
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,20 @@ export {
|
|
|
9
9
|
getApplePayButtonInitialHeight,
|
|
10
10
|
getApplePayButtonSrc,
|
|
11
11
|
} from "./apple-pay";
|
|
12
|
-
|
|
12
|
+
export type {
|
|
13
|
+
PaymentMethodFormSkeleton,
|
|
14
|
+
PaymentMethodFormSkeletonKind,
|
|
15
|
+
PaymentMethodFormSkeletonOptions,
|
|
16
|
+
WalletButtonSkeleton,
|
|
17
|
+
WalletButtonSkeletonOptions,
|
|
18
|
+
} from "./form-skeleton";
|
|
19
|
+
export {
|
|
20
|
+
createPaymentMethodFormSkeleton,
|
|
21
|
+
createWalletButtonSkeleton,
|
|
22
|
+
ensureSkeletonStyles,
|
|
23
|
+
resolveWalletButtonSkeletonBorderRadius,
|
|
24
|
+
SKELETON_STYLES,
|
|
25
|
+
} from "./form-skeleton";
|
|
13
26
|
export type {
|
|
14
27
|
FormattedGooglePayPaymentData,
|
|
15
28
|
GooglePayButtonController,
|
|
@@ -21,7 +34,6 @@ export {
|
|
|
21
34
|
getGooglePayButtonInitialHeight,
|
|
22
35
|
getGooglePayButtonSrc,
|
|
23
36
|
} from "./google-pay";
|
|
24
|
-
|
|
25
37
|
export { decodeJwt, getEmbedOrigin } from "./jwt";
|
|
26
38
|
|
|
27
39
|
export {
|
package/src/messaging.ts
CHANGED
|
@@ -114,7 +114,8 @@ export function updateGooglePayButton({
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Push the express-checkout amount into the embedded
|
|
117
|
+
* Push the express-checkout amount into the embedded wallet iframe.
|
|
118
|
+
* `amount` is a major-currency decimal string (e.g. `"50.00"` for $50.00).
|
|
118
119
|
*/
|
|
119
120
|
export function updateAmount({
|
|
120
121
|
iframe,
|
package/src/mount.ts
CHANGED
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
} from "./apple-pay";
|
|
8
8
|
import {
|
|
9
9
|
createPaymentMethodFormSkeleton,
|
|
10
|
+
createWalletButtonSkeleton,
|
|
10
11
|
type PaymentMethodFormSkeletonOptions,
|
|
12
|
+
resolveWalletButtonSkeletonBorderRadius,
|
|
13
|
+
type WalletButtonSkeleton,
|
|
14
|
+
type WalletButtonSkeletonOptions,
|
|
11
15
|
} from "./form-skeleton";
|
|
12
16
|
import {
|
|
13
17
|
attachGooglePayButtonListeners,
|
|
@@ -59,6 +63,19 @@ const WALLET_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
|
59
63
|
border: "0",
|
|
60
64
|
};
|
|
61
65
|
|
|
66
|
+
const WALLET_SKELETON_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
67
|
+
position: "absolute",
|
|
68
|
+
top: "0",
|
|
69
|
+
left: "0",
|
|
70
|
+
width: "100%",
|
|
71
|
+
height: "100%",
|
|
72
|
+
margin: "0",
|
|
73
|
+
opacity: "0",
|
|
74
|
+
// Do not interpolate opacity while the skeleton is showing.
|
|
75
|
+
transition: "none",
|
|
76
|
+
pointerEvents: "none",
|
|
77
|
+
};
|
|
78
|
+
|
|
62
79
|
const SKELETON_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
63
80
|
position: "absolute",
|
|
64
81
|
top: "0",
|
|
@@ -231,6 +248,141 @@ function mountPaymentMethodFormWithSkeleton({
|
|
|
231
248
|
};
|
|
232
249
|
}
|
|
233
250
|
|
|
251
|
+
type WalletListenerOptions = {
|
|
252
|
+
height?: string;
|
|
253
|
+
onHeightChange?: (height: string) => void;
|
|
254
|
+
onAppearanceReady?: () => void;
|
|
255
|
+
buttonProps?: {
|
|
256
|
+
buttonRadius?: number;
|
|
257
|
+
style?: Record<string, string | number | undefined>;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
type WalletListenerController<TOptions> = {
|
|
262
|
+
update: (patch: Partial<TOptions>) => void;
|
|
263
|
+
destroy: () => void;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
function mountWalletButtonWithSkeleton<TOptions extends WalletListenerOptions>({
|
|
267
|
+
host,
|
|
268
|
+
iframe,
|
|
269
|
+
listenerOptions,
|
|
270
|
+
iframeStyle,
|
|
271
|
+
attachListeners,
|
|
272
|
+
}: {
|
|
273
|
+
host: HTMLElement;
|
|
274
|
+
iframe: HTMLIFrameElement;
|
|
275
|
+
listenerOptions: TOptions;
|
|
276
|
+
iframeStyle?: Partial<CSSStyleDeclaration>;
|
|
277
|
+
attachListeners: (
|
|
278
|
+
iframe: HTMLIFrameElement,
|
|
279
|
+
options: TOptions,
|
|
280
|
+
) => WalletListenerController<TOptions>;
|
|
281
|
+
}): {
|
|
282
|
+
iframe: HTMLIFrameElement;
|
|
283
|
+
update: (patch: Partial<TOptions>) => void;
|
|
284
|
+
destroy: () => void;
|
|
285
|
+
} {
|
|
286
|
+
const initialHeight = listenerOptions.height ?? "48px";
|
|
287
|
+
let skeletonOptions: WalletButtonSkeletonOptions = {
|
|
288
|
+
height: initialHeight,
|
|
289
|
+
borderRadius: resolveWalletButtonSkeletonBorderRadius({
|
|
290
|
+
iframeStyle,
|
|
291
|
+
buttonProps: listenerOptions.buttonProps,
|
|
292
|
+
}),
|
|
293
|
+
};
|
|
294
|
+
const skeleton: WalletButtonSkeleton =
|
|
295
|
+
createWalletButtonSkeleton(skeletonOptions);
|
|
296
|
+
|
|
297
|
+
const wrapper = document.createElement("div");
|
|
298
|
+
wrapper.style.position = "relative";
|
|
299
|
+
wrapper.style.width = "100%";
|
|
300
|
+
wrapper.style.height = skeletonOptions.height;
|
|
301
|
+
wrapper.style.overflow = "hidden";
|
|
302
|
+
wrapper.setAttribute("aria-busy", "true");
|
|
303
|
+
|
|
304
|
+
Object.assign(iframe.style, WALLET_SKELETON_IFRAME_STYLE);
|
|
305
|
+
iframe.style.height = "100%";
|
|
306
|
+
|
|
307
|
+
wrapper.append(skeleton.element, iframe);
|
|
308
|
+
host.appendChild(wrapper);
|
|
309
|
+
|
|
310
|
+
let revealed = false;
|
|
311
|
+
let appearanceReady = false;
|
|
312
|
+
let fallbackRevealTimer: ReturnType<typeof setTimeout> | undefined;
|
|
313
|
+
let currentOptions = listenerOptions;
|
|
314
|
+
|
|
315
|
+
function reveal(): void {
|
|
316
|
+
if (revealed) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
revealed = true;
|
|
320
|
+
if (fallbackRevealTimer !== undefined) {
|
|
321
|
+
clearTimeout(fallbackRevealTimer);
|
|
322
|
+
fallbackRevealTimer = undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
iframe.style.opacity = "1";
|
|
326
|
+
iframe.style.pointerEvents = "";
|
|
327
|
+
skeleton.element.remove();
|
|
328
|
+
wrapper.removeAttribute("aria-busy");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function tryReveal(): void {
|
|
332
|
+
if (revealed || !appearanceReady) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
reveal();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const controller = attachListeners(iframe, {
|
|
339
|
+
...listenerOptions,
|
|
340
|
+
onHeightChange: (height) => {
|
|
341
|
+
currentOptions.onHeightChange?.(height);
|
|
342
|
+
},
|
|
343
|
+
onAppearanceReady: () => {
|
|
344
|
+
appearanceReady = true;
|
|
345
|
+
tryReveal();
|
|
346
|
+
currentOptions.onAppearanceReady?.();
|
|
347
|
+
},
|
|
348
|
+
} as TOptions);
|
|
349
|
+
|
|
350
|
+
fallbackRevealTimer = setTimeout(() => {
|
|
351
|
+
reveal();
|
|
352
|
+
}, 1500);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
iframe,
|
|
356
|
+
update(patch) {
|
|
357
|
+
currentOptions = { ...currentOptions, ...patch };
|
|
358
|
+
// Keep the wrapped reveal listeners. Forwarding these would replace
|
|
359
|
+
// them and leave the iframe at opacity 0.
|
|
360
|
+
const rest = { ...patch };
|
|
361
|
+
delete rest.onAppearanceReady;
|
|
362
|
+
delete rest.onHeightChange;
|
|
363
|
+
controller.update(rest);
|
|
364
|
+
skeletonOptions = {
|
|
365
|
+
height: currentOptions.height ?? skeletonOptions.height,
|
|
366
|
+
borderRadius: resolveWalletButtonSkeletonBorderRadius({
|
|
367
|
+
iframeStyle,
|
|
368
|
+
buttonProps: currentOptions.buttonProps,
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
wrapper.style.height = skeletonOptions.height;
|
|
372
|
+
if (!revealed) {
|
|
373
|
+
skeleton.update(skeletonOptions);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
destroy() {
|
|
377
|
+
if (fallbackRevealTimer !== undefined) {
|
|
378
|
+
clearTimeout(fallbackRevealTimer);
|
|
379
|
+
}
|
|
380
|
+
controller.destroy();
|
|
381
|
+
wrapper.remove();
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
234
386
|
/**
|
|
235
387
|
* Options accepted by {@link mountAmosCreditCardPaymentMethodForm}.
|
|
236
388
|
*/
|
|
@@ -420,6 +572,9 @@ export type AmosGooglePayButtonMountController = GooglePayButtonController & {
|
|
|
420
572
|
* Mount the secure Google Pay button (express checkout) into a
|
|
421
573
|
* container element. Returns a controller exposing the underlying
|
|
422
574
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
575
|
+
*
|
|
576
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
577
|
+
* iframe once appearance is applied.
|
|
423
578
|
*/
|
|
424
579
|
export function mountAmosGooglePayButton(
|
|
425
580
|
container: Container,
|
|
@@ -439,28 +594,14 @@ export function mountAmosGooglePayButton(
|
|
|
439
594
|
style: WALLET_IFRAME_STYLE,
|
|
440
595
|
});
|
|
441
596
|
Object.assign(iframe.style, iframeStyle);
|
|
442
|
-
host.appendChild(iframe);
|
|
443
597
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
onHeightChange: (height) => {
|
|
447
|
-
iframe.style.height = height;
|
|
448
|
-
listenerOptions.onHeightChange?.(height);
|
|
449
|
-
},
|
|
450
|
-
onAppearanceReady: () => {
|
|
451
|
-
iframe.style.opacity = "1";
|
|
452
|
-
listenerOptions.onAppearanceReady?.();
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
return {
|
|
598
|
+
return mountWalletButtonWithSkeleton({
|
|
599
|
+
host,
|
|
457
600
|
iframe,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
},
|
|
463
|
-
};
|
|
601
|
+
listenerOptions,
|
|
602
|
+
iframeStyle,
|
|
603
|
+
attachListeners: attachGooglePayButtonListeners,
|
|
604
|
+
});
|
|
464
605
|
}
|
|
465
606
|
|
|
466
607
|
/**
|
|
@@ -497,6 +638,9 @@ export type AmosApplePayButtonMountController = ApplePayButtonController & {
|
|
|
497
638
|
* Mount the secure Apple Pay button (express checkout) into a
|
|
498
639
|
* container element. Returns a controller exposing the underlying
|
|
499
640
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
641
|
+
*
|
|
642
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
643
|
+
* iframe once appearance is applied.
|
|
500
644
|
*/
|
|
501
645
|
export function mountAmosApplePayButton(
|
|
502
646
|
container: Container,
|
|
@@ -516,26 +660,12 @@ export function mountAmosApplePayButton(
|
|
|
516
660
|
style: WALLET_IFRAME_STYLE,
|
|
517
661
|
});
|
|
518
662
|
Object.assign(iframe.style, iframeStyle);
|
|
519
|
-
host.appendChild(iframe);
|
|
520
|
-
|
|
521
|
-
const controller = attachApplePayButtonListeners(iframe, {
|
|
522
|
-
...listenerOptions,
|
|
523
|
-
onHeightChange: (height) => {
|
|
524
|
-
iframe.style.height = height;
|
|
525
|
-
listenerOptions.onHeightChange?.(height);
|
|
526
|
-
},
|
|
527
|
-
onAppearanceReady: () => {
|
|
528
|
-
iframe.style.opacity = "1";
|
|
529
|
-
listenerOptions.onAppearanceReady?.();
|
|
530
|
-
},
|
|
531
|
-
});
|
|
532
663
|
|
|
533
|
-
return {
|
|
664
|
+
return mountWalletButtonWithSkeleton({
|
|
665
|
+
host,
|
|
534
666
|
iframe,
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
},
|
|
540
|
-
};
|
|
667
|
+
listenerOptions,
|
|
668
|
+
iframeStyle,
|
|
669
|
+
attachListeners: attachApplePayButtonListeners,
|
|
670
|
+
});
|
|
541
671
|
}
|
package/src/types.ts
CHANGED
|
@@ -406,7 +406,10 @@ export type Message =
|
|
|
406
406
|
height: string;
|
|
407
407
|
}
|
|
408
408
|
| {
|
|
409
|
-
/**
|
|
409
|
+
/**
|
|
410
|
+
* Parent → embed: express-checkout amount changed. Major-currency
|
|
411
|
+
* decimal string (e.g. `"50.00"` for $50.00).
|
|
412
|
+
*/
|
|
410
413
|
type: "UPDATE_AMOUNT";
|
|
411
414
|
amount: string;
|
|
412
415
|
}
|