@zonos/elements 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/index.cjs.js +7 -7
  2. package/dist/cjs/zonos-cart-items_12.cjs.entry.js +66 -3
  3. package/dist/cjs/zonos-checkout.cjs.entry.js +97 -10
  4. package/dist/collection/components/checkout/zonos-checkout/zonos-cart-items/zonos-cart-items.css +10 -0
  5. package/dist/collection/components/checkout/zonos-checkout/zonos-cart-items/zonos-cart-items.js +6 -2
  6. package/dist/collection/components/checkout/zonos-checkout/zonos-checkout.js +7 -10
  7. package/dist/collection/components/utils/checkout/isBigCommerceCheckoutBypassEnabled.js +74 -0
  8. package/dist/collection/components/utils/checkout/isHostedVsNativeTestAllowed.js +13 -0
  9. package/dist/collection/components/utils/checkout/itemCustomization.js +53 -0
  10. package/dist/collection/components/utils/checkout/parseItemCustomizations.js +6 -0
  11. package/dist/collection/components/utils/checkout/watchImmediateBigcommerceDomesticCheckoutRedirect.js +8 -0
  12. package/dist/collection/types/checkout/itemCustomization.js +1 -0
  13. package/dist/collection/types/index.js +1 -0
  14. package/dist/esm/index.js +7 -7
  15. package/dist/esm/zonos-cart-items_12.entry.js +66 -3
  16. package/dist/esm/zonos-checkout.entry.js +97 -10
  17. package/dist/esm-es5/index.js +4 -4
  18. package/dist/esm-es5/zonos-cart-items_12.entry.js +23 -10
  19. package/dist/esm-es5/zonos-checkout.entry.js +44 -19
  20. package/dist/scripts/index.d.ts +14 -0
  21. package/dist/scripts/index.js +1 -1
  22. package/dist/scripts/loadZonos.js +1 -1
  23. package/dist/scripts/zonos.js +1 -1
  24. package/dist/types/components/utils/checkout/isBigCommerceCheckoutBypassEnabled.d.ts +11 -0
  25. package/dist/types/components/utils/checkout/isHostedVsNativeTestAllowed.d.ts +11 -0
  26. package/dist/types/components/utils/checkout/itemCustomization.d.ts +23 -0
  27. package/dist/types/components/utils/checkout/parseItemCustomizations.d.ts +7 -0
  28. package/dist/types/types/checkout/CheckoutConfig.d.ts +14 -0
  29. package/dist/types/types/checkout/itemCustomization.d.ts +4 -0
  30. package/dist/types/types/index.d.ts +1 -0
  31. package/dist/zonos-elements/index.esm.js +1 -1
  32. package/dist/zonos-elements/p-30b3c9d3.entry.js +1 -0
  33. package/dist/zonos-elements/p-5873ab8e.entry.js +1 -0
  34. package/dist/zonos-elements/{p-5236c676.system.entry.js → p-7f031592.system.entry.js} +84 -59
  35. package/dist/zonos-elements/p-Bu9WCfUC.system.js +1 -1
  36. package/dist/zonos-elements/{p-DPZSD7Kg.system.js → p-CXxvn4GX.system.js} +4 -4
  37. package/dist/zonos-elements/{p-5016d71b.system.entry.js → p-d82fd8a9.system.entry.js} +68 -54
  38. package/dist/zonos-elements/zonos-elements.esm.js +1 -1
  39. package/item-customization.d.ts +25 -0
  40. package/item-customization.js +52 -0
  41. package/package.json +7 -4
  42. package/dist/zonos-elements/p-34656e6c.entry.js +0 -1
  43. package/dist/zonos-elements/p-f8b50aa0.entry.js +0 -1
@@ -131,7 +131,7 @@ const getZonosQaUrl = () => {
131
131
  return null;
132
132
  }
133
133
  const qaUrl = extractZonosQaUrl(window.location.href);
134
- const cdnUrl = "https://v1.4.1.js.zonos.com";
134
+ const cdnUrl = "https://v1.5.0.js.zonos.com";
135
135
  // when the qaUrl is not set and the built URL is from the test domain, use the test domain for testing
136
136
  if (!qaUrl &&
137
137
  (cdnUrl.includes('test.') ||
@@ -420,10 +420,10 @@ const zonosLoadUrl = (() => {
420
420
  return null;
421
421
  })();
422
422
  const startLoadTime = Date.now();
423
- const ZONOS_URL = "https://v1.4.1.js.zonos.com";
424
- const JS_DELIVR_ZONOS_URL = zonosLoadUrl || "https://cdn.jsdelivr.net/npm/@zonos/elements@1.4.1";
425
- const UNPKG_ZONOS_URL = "https://unpkg.com/@zonos/elements@1.4.1";
426
- const CDNJS_ZONOS_URL = "https://cdnjs.cloudflare.com/ajax/libs/zonos-elements/1.4.1";
423
+ const ZONOS_URL = "https://v1.5.0.js.zonos.com";
424
+ const JS_DELIVR_ZONOS_URL = zonosLoadUrl || "https://cdn.jsdelivr.net/npm/@zonos/elements@1.5.0";
425
+ const UNPKG_ZONOS_URL = "https://unpkg.com/@zonos/elements@1.5.0";
426
+ const CDNJS_ZONOS_URL = "https://cdnjs.cloudflare.com/ajax/libs/zonos-elements/1.5.0";
427
427
  const GOOGLE_FONT_URL = 'https://fonts.googleapis.com';
428
428
  const GOOGLE_FONT_REGEX = /^https:\/\/fonts\.googleapis\.com/;
429
429
  class Zonos {
@@ -441,7 +441,7 @@ Zonos.iframeLogType = typeof window !== 'undefined'
441
441
  ? new URLSearchParams(window.location.search).get('iframeLogType')
442
442
  : null;
443
443
  Zonos.isBigCommerce = false;
444
- Zonos.releaseDate = "1792428736539";
444
+ Zonos.releaseDate = "1792773585238";
445
445
  Zonos.isLegacyCheckout = false;
446
446
  Zonos.googleAnalyticId = null;
447
447
  Zonos.googleAnalyticReady = false;
@@ -466,7 +466,7 @@ Zonos.isNpm = true;
466
466
  Zonos.zonosQaUrl = null;
467
467
  Zonos.zonosQaUrlApi = null;
468
468
  Zonos.trackEventLogs = [];
469
- Zonos.version = `v${"1.4.1"}`;
469
+ Zonos.version = `v${"1.5.0"}`;
470
470
  Zonos.modeAlerted = false;
471
471
  Zonos.cartData = null;
472
472
  Zonos.paypal = null;
@@ -30,7 +30,67 @@ require('./debounce-Cd2emZPn.js');
30
30
  require('./debounce-vY0QYT6o.js');
31
31
  require('./ShortAddressCode-C5l520-F.js');
32
32
 
33
- const zonosCartItemsCss = () => `:host{font-family:var(--zonos--font-family, Inter), Inter;display:block;white-space:normal}:host *{font-family:var(--zonos--font-family, Inter), Inter}:host img{max-width:100%}:host dl,:host dd,:host h1,:host h2,:host h3,:host h4,:host h5,:host h6,:host hr,:host figure,:host p,:host pre{margin:0}:host{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}:host .zonos--cart-table{width:100%;border:none;border-collapse:collapse;overflow:auto;font-size:var(--zonos-elements-font-size-base)}:host .zonos--cart-table .zonos--column{padding:8px;font-size:var(--zonos-elements-font-size-base)}:host .zonos--cart-table .zonos--column:last-child{text-align:right}:host .zonos--cart-table .zonos--column:first-child{width:60px}:host .zonos--cart-table td{vertical-align:top}:host .zonos--cart-item-name{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:12px}:host .zonos--cart-item-name .zonos--cart-item-quantity>*{display:inline-block}:host .zonos--cart-item-price zonos-text{white-space:nowrap}:host img{min-width:40px;margin-top:6px}`;
33
+ /**
34
+ * Import-free so `scripts/buildItemCustomization.mjs` can emit one module.
35
+ * demo-app imports the generated `item-customization.js`, not this file.
36
+ */
37
+ const ITEM_CUSTOMIZATION_ATTRIBUTE_KEY = 'itemCustomization';
38
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
39
+ /** Drops an empty name, a non-string value, and the unselected value "0". */
40
+ const readDisplayableItemCustomization = (value) => {
41
+ if (!isRecord(value)) {
42
+ return null;
43
+ }
44
+ const { name } = value;
45
+ if (typeof name !== 'string' || name === '') {
46
+ return null;
47
+ }
48
+ const choiceValue = value.value;
49
+ if (typeof choiceValue !== 'string' || choiceValue === '0') {
50
+ return null;
51
+ }
52
+ return {
53
+ name,
54
+ value: choiceValue,
55
+ };
56
+ };
57
+ const parseJson = (value) => {
58
+ try {
59
+ // The generated package entry cannot import the shared jsonParse helper.
60
+ // eslint-disable-next-line no-restricted-syntax
61
+ return JSON.parse(value);
62
+ }
63
+ catch (_a) {
64
+ return null;
65
+ }
66
+ };
67
+ /** Selected customization entries, with the original object for extra ids. */
68
+ const readItemCustomizationEntries = (attributes) => {
69
+ var _a;
70
+ const rawValue = (_a = attributes === null || attributes === void 0 ? void 0 : attributes.find(attribute => (attribute === null || attribute === void 0 ? void 0 : attribute.key) === ITEM_CUSTOMIZATION_ATTRIBUTE_KEY)) === null || _a === void 0 ? void 0 : _a.value;
71
+ if (!rawValue) {
72
+ return [];
73
+ }
74
+ const parsed = parseJson(rawValue);
75
+ if (!Array.isArray(parsed)) {
76
+ return [];
77
+ }
78
+ return parsed.flatMap(entry => {
79
+ const choice = readDisplayableItemCustomization(entry);
80
+ if (!choice || !isRecord(entry)) {
81
+ return [];
82
+ }
83
+ return [{ choice, raw: entry }];
84
+ });
85
+ };
86
+
87
+ /**
88
+ * Reads a cart row's `itemCustomization` attribute into display pairs.
89
+ * Unreadable data is an empty list.
90
+ */
91
+ const parseItemCustomizations = (attributes) => readItemCustomizationEntries(attributes).map(entry => entry.choice);
92
+
93
+ const zonosCartItemsCss = () => `:host{font-family:var(--zonos--font-family, Inter), Inter;display:block;white-space:normal}:host *{font-family:var(--zonos--font-family, Inter), Inter}:host img{max-width:100%}:host dl,:host dd,:host h1,:host h2,:host h3,:host h4,:host h5,:host h6,:host hr,:host figure,:host p,:host pre{margin:0}:host{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}:host .zonos--cart-table{width:100%;border:none;border-collapse:collapse;overflow:auto;font-size:var(--zonos-elements-font-size-base)}:host .zonos--cart-table .zonos--column{padding:8px;font-size:var(--zonos-elements-font-size-base)}:host .zonos--cart-table .zonos--column:last-child{text-align:right}:host .zonos--cart-table .zonos--column:first-child{width:60px}:host .zonos--cart-table td{vertical-align:top}:host .zonos--cart-item-name{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:12px}:host .zonos--cart-item-name .zonos--cart-item-choices{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:4px;min-width:0}:host .zonos--cart-item-name .zonos--cart-item-choices zonos-text{overflow-wrap:break-word;white-space:normal}:host .zonos--cart-item-name .zonos--cart-item-quantity>*{display:inline-block}:host .zonos--cart-item-price zonos-text{white-space:nowrap}:host img{min-width:40px;margin-top:6px}`;
34
94
 
35
95
  const ZonosCartItems = class {
36
96
  constructor(hostRef) {
@@ -40,15 +100,18 @@ const ZonosCartItems = class {
40
100
  render() {
41
101
  const emptyItemImg = index.getAssetPath('./assets/empty-item.jpeg');
42
102
  const nonRestrictedItems = cart.cartStore.items.filter(item => !item.isRestricted);
43
- return (index.h(index.Host, { key: '2f81af1b6315853352a29bacaa230d782e76115a', class: "zonos--cart-items zonos--elements" }, index.h("table", { key: '7fe64f09729e0791605cdd8e66d7230ab162c7cf', class: "zonos--cart-table" }, index.h("tbody", { key: '49c17b5c7c68bfd41b2d8fabbbf4c3c764faba33' }, nonRestrictedItems.map(item => {
103
+ return (index.h(index.Host, { key: 'eeb10f76fbab43d256e6130fe2ab88a33c1598aa', class: "zonos--cart-items zonos--elements" }, index.h("table", { key: '61ae44a57c1386f5ca6afb17c388ae9fcf29d8a1', class: "zonos--cart-table" }, index.h("tbody", { key: '8471ae46186543cee754685e0182c4057d982967' }, nonRestrictedItems.map(item => {
44
104
  const itemImg = item.imageUrl;
45
105
  const cleanedItemNames = cleanHtmlMarkdown.cleanHtmlMarkdown(item.name);
106
+ const choices = parseItemCustomizations(item.attributes);
46
107
  const amount = item.amount * item.quantity;
47
108
  return (index.h("tr", { class: "zonos--cart-item" }, index.h("td", { class: "zonos--column zonos--cart-item-image" }, itemImg && !this.errorImages.includes(itemImg) ? (index.h("img", { alt: item.name, onError: () => {
48
109
  this.errorImages = [...this.errorImages, itemImg];
49
110
  }, src: item.imageUrl, style: {
50
111
  'min-width': '40px',
51
- }, width: 40 })) : (index.h("img", { alt: item.name, src: emptyItemImg, width: 40 }))), index.h("td", { class: "zonos--column zonos--cart-item-name" }, index.h("div", null, cleanedItemNames.map(name => (index.h("zonos-text", { type: "label" }, name)))), index.h("zonos-text", { textColor: "gray-600", type: "caption" }, localization.translate('Quantity'), ": ", item.quantity)), index.h("td", { class: "zonos--column zonos--cart-item-price" }, index.h("zonos-text", { type: "semiBoldLabel", weight: 600 }, renderCheckoutCurrency.renderCheckoutCurrency({
112
+ }, width: 40 })) : (index.h("img", { alt: item.name, src: emptyItemImg, width: 40 }))), index.h("td", { class: "zonos--column zonos--cart-item-name" }, index.h("div", null, cleanedItemNames.map(name => (index.h("zonos-text", { type: "label" }, name)))), choices.length > 0 && (index.h("div", { class: "zonos--cart-item-choices" }, choices.map(choice => (index.h("zonos-text", { textColor: "gray-600", type: "caption" }, choice.value === ''
113
+ ? choice.name
114
+ : `${choice.name}: ${choice.value}`))))), index.h("zonos-text", { textColor: "gray-600", type: "caption" }, localization.translate('Quantity'), ": ", item.quantity)), index.h("td", { class: "zonos--column zonos--cart-item-price" }, index.h("zonos-text", { type: "semiBoldLabel", weight: 600 }, renderCheckoutCurrency.renderCheckoutCurrency({
52
115
  amount,
53
116
  isForeign: false,
54
117
  })))));
@@ -675,6 +675,20 @@ const isDisabledCountry = (showLog = true) => {
675
675
  return false;
676
676
  };
677
677
 
678
+ /**
679
+ * BigCommerce checkout depends on storefront callbacks (clear cart, inventory).
680
+ * The hosted variant leaves the storefront, so the test does not apply there.
681
+ */
682
+ const isHostedVsNativeTestAllowed = ({ createCartId, enabledTestKeys, isBigCommerce, }) => {
683
+ if (isBigCommerce) {
684
+ return false;
685
+ }
686
+ if (typeof createCartId !== 'function') {
687
+ return false;
688
+ }
689
+ return enabledTestKeys.includes('hosted_vs_native');
690
+ };
691
+
678
692
  const isShippableCountry = (showLog = true,
679
693
  /**
680
694
  * If provided, will check if the country is shippable based on the provided country code instead of the Hello selected country code
@@ -700,6 +714,76 @@ providedCountryCode) => {
700
714
  return true;
701
715
  };
702
716
 
717
+ const ZBYPASS_ON_VALUES = ['1', 'true', 'yes'];
718
+ /**
719
+ * Page-load result of reading the checkout `zBypass` query. Later calls on the
720
+ * same checkout URL reuse this so stripping the parameter does not change the
721
+ * answer.
722
+ */
723
+ let consumedZBypassQuery = null;
724
+ const isCheckoutPage = () => {
725
+ return window.location.pathname.includes('/checkout');
726
+ };
727
+ const isAcceptedZBypassQuery = (rawValue) => {
728
+ if (rawValue === null) {
729
+ return false;
730
+ }
731
+ return ZBYPASS_ON_VALUES.includes(rawValue.trim().toLowerCase());
732
+ };
733
+ /**
734
+ * True only when zonos-settings set
735
+ * `checkoutSettings.plugins.bigcommerce.allowBypassQuery` to `true`.
736
+ */
737
+ const isBypassQueryAllowed = () => {
738
+ var _a, _b;
739
+ return (((_b = (_a = zonosStore.zonosStore.checkoutSettings.plugins) === null || _a === void 0 ? void 0 : _a.bigcommerce) === null || _b === void 0 ? void 0 : _b.allowBypassQuery) === true);
740
+ };
741
+ /**
742
+ * Read an accepted `zBypass` query on the checkout page once, then remove it
743
+ * from the URL. A later call on that same URL returns the same answer.
744
+ */
745
+ const readCheckoutZBypassQuery = () => {
746
+ const { pathname, search } = window.location;
747
+ if (consumedZBypassQuery &&
748
+ consumedZBypassQuery.pathname === pathname &&
749
+ consumedZBypassQuery.search === search) {
750
+ return consumedZBypassQuery.enabled;
751
+ }
752
+ const params = new URLSearchParams(search);
753
+ const accepted = isAcceptedZBypassQuery(params.get('zBypass'));
754
+ if (!accepted) {
755
+ consumedZBypassQuery = { enabled: false, pathname, search };
756
+ return false;
757
+ }
758
+ params.delete('zBypass');
759
+ const nextSearch = params.toString();
760
+ const hash = window.location.hash || '';
761
+ const nextUrl = `${pathname}${nextSearch ? `?${nextSearch}` : ''}${hash}`;
762
+ window.history.replaceState(window.history.state, document.title, nextUrl);
763
+ const enabled = isBypassQueryAllowed();
764
+ consumedZBypassQuery = {
765
+ enabled,
766
+ pathname,
767
+ search: window.location.search,
768
+ };
769
+ return enabled;
770
+ };
771
+ /**
772
+ * True when this BigCommerce checkout page should skip address detection.
773
+ * That is only when zonos-settings allows the `zBypass` query and this
774
+ * checkout URL has an accepted value. Storefront pages are never bypassed, so
775
+ * the query stays in the URL until checkout. The query is removed once, and
776
+ * later calls on that checkout URL keep the same answer. Nothing is written
777
+ * to storage.
778
+ */
779
+ const isBigCommerceCheckoutBypassEnabled = () => {
780
+ var _a;
781
+ if (!((_a = window.Zonos) === null || _a === void 0 ? void 0 : _a.isBigCommerce) || !isCheckoutPage()) {
782
+ return false;
783
+ }
784
+ return readCheckoutZBypassQuery();
785
+ };
786
+
703
787
  const FALLBACK_SHIP_TO_COUNTRY_SELECTOR = 'select[name="shippingAddress.countryCode"]';
704
788
  /**
705
789
  * Native BigCommerce shipping-step "Continue" button. Clicking it commits the
@@ -800,6 +884,13 @@ const watchImmediateBigcommerceDomesticCheckoutRedirect = ({ domesticRedirectedB
800
884
  if (!window.Zonos.isBigCommerce) {
801
885
  return;
802
886
  }
887
+ // Only the native checkout page skips address detection. Storefront pages
888
+ // still run the domestic-session handoff below, and they do not consume
889
+ // a `zBypass` query, so the parameter can survive until `/checkout`.
890
+ if (window.location.pathname.includes('/checkout') &&
891
+ isBigCommerceCheckoutBypassEnabled()) {
892
+ return;
893
+ }
803
894
  /**
804
895
  * Keep checking.
805
896
  * @returns `true` if the caller should stop polling — either because the user
@@ -1605,16 +1696,12 @@ const ZonosCheckout = class {
1605
1696
  };
1606
1697
  };
1607
1698
  this.shouldAllowHostedVsNativeTest = () => {
1608
- // This test is only applicable to checkout that's using createCartid callback
1609
- if (typeof zonosStore.zonosStore.checkoutSettings.createCartId !== 'function') {
1610
- return false;
1611
- }
1612
- const hostedVsNativeTest = zonosStore.zonosStore.enabledVariantTestKeys.includes('hosted_vs_native');
1613
- // Should not do anything if the test is not enabled
1614
- if (!hostedVsNativeTest) {
1615
- return false;
1616
- }
1617
- return true;
1699
+ var _a;
1700
+ return isHostedVsNativeTestAllowed({
1701
+ createCartId: zonosStore.zonosStore.checkoutSettings.createCartId,
1702
+ enabledTestKeys: zonosStore.zonosStore.enabledVariantTestKeys,
1703
+ isBigCommerce: Boolean((_a = window.Zonos) === null || _a === void 0 ? void 0 : _a.isBigCommerce),
1704
+ });
1618
1705
  };
1619
1706
  // Register test hosted vs native if needed to get the variant selection for this test
1620
1707
  this.registerTestHostedVsNative = async () => {
@@ -56,6 +56,16 @@
56
56
  flex-direction: column;
57
57
  gap: 12px;
58
58
  }
59
+ :host .zonos--cart-item-name .zonos--cart-item-choices {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 4px;
63
+ min-width: 0;
64
+ }
65
+ :host .zonos--cart-item-name .zonos--cart-item-choices zonos-text {
66
+ overflow-wrap: break-word;
67
+ white-space: normal;
68
+ }
59
69
  :host .zonos--cart-item-name .zonos--cart-item-quantity > * {
60
70
  display: inline-block;
61
71
  }
@@ -1,6 +1,7 @@
1
1
  import { getAssetPath, h, Host } from "@stencil/core";
2
2
  import { cartStore } from "../../../store/checkout/cart";
3
3
  import { translate } from "../../../store/translationStore";
4
+ import { parseItemCustomizations } from "../../../utils/checkout/parseItemCustomizations";
4
5
  import { renderCheckoutCurrency } from "../../../utils/checkout/renderCheckoutCurrency";
5
6
  import { cleanHtmlMarkdown } from "../../../utils/cleanHtmlMarkdown";
6
7
  export class ZonosCartItems {
@@ -10,15 +11,18 @@ export class ZonosCartItems {
10
11
  render() {
11
12
  const emptyItemImg = getAssetPath('./assets/empty-item.jpeg');
12
13
  const nonRestrictedItems = cartStore.items.filter(item => !item.isRestricted);
13
- return (h(Host, { key: '2f81af1b6315853352a29bacaa230d782e76115a', class: "zonos--cart-items zonos--elements" }, h("table", { key: '7fe64f09729e0791605cdd8e66d7230ab162c7cf', class: "zonos--cart-table" }, h("tbody", { key: '49c17b5c7c68bfd41b2d8fabbbf4c3c764faba33' }, nonRestrictedItems.map(item => {
14
+ return (h(Host, { key: 'eeb10f76fbab43d256e6130fe2ab88a33c1598aa', class: "zonos--cart-items zonos--elements" }, h("table", { key: '61ae44a57c1386f5ca6afb17c388ae9fcf29d8a1', class: "zonos--cart-table" }, h("tbody", { key: '8471ae46186543cee754685e0182c4057d982967' }, nonRestrictedItems.map(item => {
14
15
  const itemImg = item.imageUrl;
15
16
  const cleanedItemNames = cleanHtmlMarkdown(item.name);
17
+ const choices = parseItemCustomizations(item.attributes);
16
18
  const amount = item.amount * item.quantity;
17
19
  return (h("tr", { class: "zonos--cart-item" }, h("td", { class: "zonos--column zonos--cart-item-image" }, itemImg && !this.errorImages.includes(itemImg) ? (h("img", { alt: item.name, onError: () => {
18
20
  this.errorImages = [...this.errorImages, itemImg];
19
21
  }, src: item.imageUrl, style: {
20
22
  'min-width': '40px',
21
- }, width: 40 })) : (h("img", { alt: item.name, src: emptyItemImg, width: 40 }))), h("td", { class: "zonos--column zonos--cart-item-name" }, h("div", null, cleanedItemNames.map(name => (h("zonos-text", { type: "label" }, name)))), h("zonos-text", { textColor: "gray-600", type: "caption" }, translate('Quantity'), ": ", item.quantity)), h("td", { class: "zonos--column zonos--cart-item-price" }, h("zonos-text", { type: "semiBoldLabel", weight: 600 }, renderCheckoutCurrency({
23
+ }, width: 40 })) : (h("img", { alt: item.name, src: emptyItemImg, width: 40 }))), h("td", { class: "zonos--column zonos--cart-item-name" }, h("div", null, cleanedItemNames.map(name => (h("zonos-text", { type: "label" }, name)))), choices.length > 0 && (h("div", { class: "zonos--cart-item-choices" }, choices.map(choice => (h("zonos-text", { textColor: "gray-600", type: "caption" }, choice.value === ''
24
+ ? choice.name
25
+ : `${choice.name}: ${choice.value}`))))), h("zonos-text", { textColor: "gray-600", type: "caption" }, translate('Quantity'), ": ", item.quantity)), h("td", { class: "zonos--column zonos--cart-item-price" }, h("zonos-text", { type: "semiBoldLabel", weight: 600 }, renderCheckoutCurrency({
22
26
  amount,
23
27
  isForeign: false,
24
28
  })))));
@@ -27,6 +27,7 @@ import { getIsStagingMode } from "../../utils/checkout/getIsStagingMode";
27
27
  import { getIsTestMode } from "../../utils/checkout/getIsTestMode";
28
28
  import { isCheckoutMobile } from "../../utils/checkout/isCheckoutMobile";
29
29
  import { isDisabledCountry } from "../../utils/checkout/isDisabledCountry";
30
+ import { isHostedVsNativeTestAllowed } from "../../utils/checkout/isHostedVsNativeTestAllowed";
30
31
  import { isShippableCountry } from "../../utils/checkout/isShippableCountry";
31
32
  import { watchImmediateBigcommerceDomesticCheckoutRedirect } from "../../utils/checkout/watchImmediateBigcommerceDomesticCheckoutRedirect";
32
33
  import { classes } from "../../utils/classes";
@@ -296,16 +297,12 @@ export class ZonosCheckout {
296
297
  };
297
298
  };
298
299
  this.shouldAllowHostedVsNativeTest = () => {
299
- // This test is only applicable to checkout that's using createCartid callback
300
- if (typeof zonosStore.checkoutSettings.createCartId !== 'function') {
301
- return false;
302
- }
303
- const hostedVsNativeTest = zonosStore.enabledVariantTestKeys.includes('hosted_vs_native');
304
- // Should not do anything if the test is not enabled
305
- if (!hostedVsNativeTest) {
306
- return false;
307
- }
308
- return true;
300
+ var _a;
301
+ return isHostedVsNativeTestAllowed({
302
+ createCartId: zonosStore.checkoutSettings.createCartId,
303
+ enabledTestKeys: zonosStore.enabledVariantTestKeys,
304
+ isBigCommerce: Boolean((_a = window.Zonos) === null || _a === void 0 ? void 0 : _a.isBigCommerce),
305
+ });
309
306
  };
310
307
  // Register test hosted vs native if needed to get the variant selection for this test
311
308
  this.registerTestHostedVsNative = async () => {
@@ -0,0 +1,74 @@
1
+ import { zonosStore } from "../../store/zonosStore";
2
+ const ZBYPASS_ON_VALUES = ['1', 'true', 'yes'];
3
+ /**
4
+ * Page-load result of reading the checkout `zBypass` query. Later calls on the
5
+ * same checkout URL reuse this so stripping the parameter does not change the
6
+ * answer.
7
+ */
8
+ let consumedZBypassQuery = null;
9
+ const isCheckoutPage = () => {
10
+ return window.location.pathname.includes('/checkout');
11
+ };
12
+ const isAcceptedZBypassQuery = (rawValue) => {
13
+ if (rawValue === null) {
14
+ return false;
15
+ }
16
+ return ZBYPASS_ON_VALUES.includes(rawValue.trim().toLowerCase());
17
+ };
18
+ /**
19
+ * True only when zonos-settings set
20
+ * `checkoutSettings.plugins.bigcommerce.allowBypassQuery` to `true`.
21
+ */
22
+ const isBypassQueryAllowed = () => {
23
+ var _a, _b;
24
+ return (((_b = (_a = zonosStore.checkoutSettings.plugins) === null || _a === void 0 ? void 0 : _a.bigcommerce) === null || _b === void 0 ? void 0 : _b.allowBypassQuery) === true);
25
+ };
26
+ /**
27
+ * Read an accepted `zBypass` query on the checkout page once, then remove it
28
+ * from the URL. A later call on that same URL returns the same answer.
29
+ */
30
+ const readCheckoutZBypassQuery = () => {
31
+ const { pathname, search } = window.location;
32
+ if (consumedZBypassQuery &&
33
+ consumedZBypassQuery.pathname === pathname &&
34
+ consumedZBypassQuery.search === search) {
35
+ return consumedZBypassQuery.enabled;
36
+ }
37
+ const params = new URLSearchParams(search);
38
+ const accepted = isAcceptedZBypassQuery(params.get('zBypass'));
39
+ if (!accepted) {
40
+ consumedZBypassQuery = { enabled: false, pathname, search };
41
+ return false;
42
+ }
43
+ params.delete('zBypass');
44
+ const nextSearch = params.toString();
45
+ const hash = window.location.hash || '';
46
+ const nextUrl = `${pathname}${nextSearch ? `?${nextSearch}` : ''}${hash}`;
47
+ window.history.replaceState(window.history.state, document.title, nextUrl);
48
+ const enabled = isBypassQueryAllowed();
49
+ consumedZBypassQuery = {
50
+ enabled,
51
+ pathname,
52
+ search: window.location.search,
53
+ };
54
+ return enabled;
55
+ };
56
+ /** Clears the page-load query result. Tests use this between cases. */
57
+ export const resetBigCommerceCheckoutBypassCache = () => {
58
+ consumedZBypassQuery = null;
59
+ };
60
+ /**
61
+ * True when this BigCommerce checkout page should skip address detection.
62
+ * That is only when zonos-settings allows the `zBypass` query and this
63
+ * checkout URL has an accepted value. Storefront pages are never bypassed, so
64
+ * the query stays in the URL until checkout. The query is removed once, and
65
+ * later calls on that checkout URL keep the same answer. Nothing is written
66
+ * to storage.
67
+ */
68
+ export const isBigCommerceCheckoutBypassEnabled = () => {
69
+ var _a;
70
+ if (!((_a = window.Zonos) === null || _a === void 0 ? void 0 : _a.isBigCommerce) || !isCheckoutPage()) {
71
+ return false;
72
+ }
73
+ return readCheckoutZBypassQuery();
74
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * BigCommerce checkout depends on storefront callbacks (clear cart, inventory).
3
+ * The hosted variant leaves the storefront, so the test does not apply there.
4
+ */
5
+ export const isHostedVsNativeTestAllowed = ({ createCartId, enabledTestKeys, isBigCommerce, }) => {
6
+ if (isBigCommerce) {
7
+ return false;
8
+ }
9
+ if (typeof createCartId !== 'function') {
10
+ return false;
11
+ }
12
+ return enabledTestKeys.includes('hosted_vs_native');
13
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Import-free so `scripts/buildItemCustomization.mjs` can emit one module.
3
+ * demo-app imports the generated `item-customization.js`, not this file.
4
+ */
5
+ export const ITEM_CUSTOMIZATION_ATTRIBUTE_KEY = 'itemCustomization';
6
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ /** Drops an empty name, a non-string value, and the unselected value "0". */
8
+ export const readDisplayableItemCustomization = (value) => {
9
+ if (!isRecord(value)) {
10
+ return null;
11
+ }
12
+ const { name } = value;
13
+ if (typeof name !== 'string' || name === '') {
14
+ return null;
15
+ }
16
+ const choiceValue = value.value;
17
+ if (typeof choiceValue !== 'string' || choiceValue === '0') {
18
+ return null;
19
+ }
20
+ return {
21
+ name,
22
+ value: choiceValue,
23
+ };
24
+ };
25
+ const parseJson = (value) => {
26
+ try {
27
+ // The generated package entry cannot import the shared jsonParse helper.
28
+ // eslint-disable-next-line no-restricted-syntax
29
+ return JSON.parse(value);
30
+ }
31
+ catch (_a) {
32
+ return null;
33
+ }
34
+ };
35
+ /** Selected customization entries, with the original object for extra ids. */
36
+ export const readItemCustomizationEntries = (attributes) => {
37
+ var _a;
38
+ const rawValue = (_a = attributes === null || attributes === void 0 ? void 0 : attributes.find(attribute => (attribute === null || attribute === void 0 ? void 0 : attribute.key) === ITEM_CUSTOMIZATION_ATTRIBUTE_KEY)) === null || _a === void 0 ? void 0 : _a.value;
39
+ if (!rawValue) {
40
+ return [];
41
+ }
42
+ const parsed = parseJson(rawValue);
43
+ if (!Array.isArray(parsed)) {
44
+ return [];
45
+ }
46
+ return parsed.flatMap(entry => {
47
+ const choice = readDisplayableItemCustomization(entry);
48
+ if (!choice || !isRecord(entry)) {
49
+ return [];
50
+ }
51
+ return [{ choice, raw: entry }];
52
+ });
53
+ };
@@ -0,0 +1,6 @@
1
+ import { readItemCustomizationEntries, } from "./itemCustomization";
2
+ /**
3
+ * Reads a cart row's `itemCustomization` attribute into display pairs.
4
+ * Unreadable data is an empty list.
5
+ */
6
+ export const parseItemCustomizations = (attributes) => readItemCustomizationEntries(attributes).map(entry => entry.choice);
@@ -5,6 +5,7 @@ import { fetchBigCommerceCheckout, fetchCheckoutId, } from "./bigcommerceStorefr
5
5
  import { buildBigcommerceRedirectUrl } from "./buildBigcommerceRedirectUrl";
6
6
  import { checkBigCommerceBypassCustomerGroup } from "./checkBigCommerceBypassCustomerGroup";
7
7
  import { checkBigCommerceForceLogin } from "./checkBigCommerceForceLogin";
8
+ import { isBigCommerceCheckoutBypassEnabled } from "./isBigCommerceCheckoutBypassEnabled";
8
9
  import { isShippableCountry } from "./isShippableCountry";
9
10
  import { log } from "../logging/log";
10
11
  import { logError } from "../logging/logError";
@@ -108,6 +109,13 @@ export const watchImmediateBigcommerceDomesticCheckoutRedirect = ({ domesticRedi
108
109
  if (!window.Zonos.isBigCommerce) {
109
110
  return;
110
111
  }
112
+ // Only the native checkout page skips address detection. Storefront pages
113
+ // still run the domestic-session handoff below, and they do not consume
114
+ // a `zBypass` query, so the parameter can survive until `/checkout`.
115
+ if (window.location.pathname.includes('/checkout') &&
116
+ isBigCommerceCheckoutBypassEnabled()) {
117
+ return;
118
+ }
111
119
  /**
112
120
  * Keep checking.
113
121
  * @returns `true` if the caller should stop polling — either because the user
@@ -14,6 +14,7 @@ export * from './checkout/api/StoreDiscountEnabled';
14
14
  export * from './checkout/api/ValidateAddress';
15
15
  export * from './checkout/api/VarianceTest';
16
16
  export * from './checkout/CheckoutConfig';
17
+ export * from './checkout/itemCustomization';
17
18
  export * from './checkout/ShortAddressCode';
18
19
  export * from './checkout/TaxId';
19
20
  export * from './ConversionEvent';
package/dist/esm/index.js CHANGED
@@ -131,7 +131,7 @@ const getZonosQaUrl = () => {
131
131
  return null;
132
132
  }
133
133
  const qaUrl = extractZonosQaUrl(window.location.href);
134
- const cdnUrl = "https://v1.4.1.js.zonos.com";
134
+ const cdnUrl = "https://v1.5.0.js.zonos.com";
135
135
  // when the qaUrl is not set and the built URL is from the test domain, use the test domain for testing
136
136
  if (!qaUrl &&
137
137
  (cdnUrl.includes('test.') ||
@@ -420,10 +420,10 @@ const zonosLoadUrl = (() => {
420
420
  return null;
421
421
  })();
422
422
  const startLoadTime = Date.now();
423
- const ZONOS_URL = "https://v1.4.1.js.zonos.com";
424
- const JS_DELIVR_ZONOS_URL = zonosLoadUrl || "https://cdn.jsdelivr.net/npm/@zonos/elements@1.4.1";
425
- const UNPKG_ZONOS_URL = "https://unpkg.com/@zonos/elements@1.4.1";
426
- const CDNJS_ZONOS_URL = "https://cdnjs.cloudflare.com/ajax/libs/zonos-elements/1.4.1";
423
+ const ZONOS_URL = "https://v1.5.0.js.zonos.com";
424
+ const JS_DELIVR_ZONOS_URL = zonosLoadUrl || "https://cdn.jsdelivr.net/npm/@zonos/elements@1.5.0";
425
+ const UNPKG_ZONOS_URL = "https://unpkg.com/@zonos/elements@1.5.0";
426
+ const CDNJS_ZONOS_URL = "https://cdnjs.cloudflare.com/ajax/libs/zonos-elements/1.5.0";
427
427
  const GOOGLE_FONT_URL = 'https://fonts.googleapis.com';
428
428
  const GOOGLE_FONT_REGEX = /^https:\/\/fonts\.googleapis\.com/;
429
429
  class Zonos {
@@ -441,7 +441,7 @@ Zonos.iframeLogType = typeof window !== 'undefined'
441
441
  ? new URLSearchParams(window.location.search).get('iframeLogType')
442
442
  : null;
443
443
  Zonos.isBigCommerce = false;
444
- Zonos.releaseDate = "1792428736539";
444
+ Zonos.releaseDate = "1792773585238";
445
445
  Zonos.isLegacyCheckout = false;
446
446
  Zonos.googleAnalyticId = null;
447
447
  Zonos.googleAnalyticReady = false;
@@ -466,7 +466,7 @@ Zonos.isNpm = true;
466
466
  Zonos.zonosQaUrl = null;
467
467
  Zonos.zonosQaUrlApi = null;
468
468
  Zonos.trackEventLogs = [];
469
- Zonos.version = `v${"1.4.1"}`;
469
+ Zonos.version = `v${"1.5.0"}`;
470
470
  Zonos.modeAlerted = false;
471
471
  Zonos.cartData = null;
472
472
  Zonos.paypal = null;