fontdue-js 3.12.0 → 3.13.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 (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/__generated__/CustomerSignInElementQuery.graphql.d.ts +22 -0
  3. package/dist/__generated__/CustomerSignInElementQuery.graphql.js +87 -0
  4. package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +1 -1
  5. package/dist/__generated__/StoreModalFamily_collection.graphql.js +8 -4
  6. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  7. package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -5
  8. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  9. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -5
  10. package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +1 -1
  11. package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +9 -5
  12. package/dist/__generated__/productState_Query.graphql.d.ts +1 -29
  13. package/dist/__generated__/productState_Query.graphql.js +15 -30
  14. package/dist/__generated__/productState_collection.graphql.d.ts +1 -1
  15. package/dist/__generated__/productState_collection.graphql.js +8 -4
  16. package/dist/__generated__/productState_fontPack.graphql.d.ts +1 -1
  17. package/dist/__generated__/productState_fontPack.graphql.js +7 -3
  18. package/dist/__generated__/productState_node.graphql.d.ts +1 -29
  19. package/dist/__generated__/productState_node.graphql.js +8 -22
  20. package/dist/__tests__/bundleSelectionOrder.test.js +0 -1
  21. package/dist/__tests__/collectionBundleSelection.test.js +31 -6
  22. package/dist/__tests__/customerSignInElement.test.js +155 -0
  23. package/dist/__tests__/customerSignInRequest.test.js +85 -0
  24. package/dist/__tests__/stripeCheckoutError.test.js +110 -0
  25. package/dist/__tests__/typeTesterPrefetchStyles.test.js +108 -0
  26. package/dist/components/CustomerSignIn/CustomerSignInElement.d.ts +22 -0
  27. package/dist/components/CustomerSignIn/CustomerSignInElement.js +132 -0
  28. package/dist/components/CustomerSignIn/index.js +4 -2
  29. package/dist/components/Root/index.js +2 -0
  30. package/dist/components/Root/productState.js +7 -33
  31. package/dist/components/StoreModal/StoreModalFamily.js +9 -5
  32. package/dist/components/StripeProvider/index.js +19 -2
  33. package/dist/components/TypeTester/TypeTesterStyleSelect.js +6 -1
  34. package/dist/components/TypeTester/usePrefetchWhenNear.d.ts +14 -0
  35. package/dist/components/TypeTester/usePrefetchWhenNear.js +69 -0
  36. package/dist/fontdue.css +10 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +1 -1
  39. package/dist/__generated__/productState_collectionBundle.graphql.d.ts +0 -41
  40. package/dist/__generated__/productState_collectionBundle.graphql.js +0 -81
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
4
+ import _CustomerSignInElementQuery from "../../__generated__/CustomerSignInElementQuery.graphql.js";
5
+ import React, { Suspense, useCallback, useState, useSyncExternalStore } from 'react';
6
+ import { ErrorBoundary } from 'react-error-boundary';
7
+ import { graphql, useLazyLoadQuery } from 'react-relay';
8
+ import CustomerSignIn from './index.js';
9
+ import { clearTokens, getTokens, subscribeToTokens } from '../../auth/tokenStore.js';
10
+ import { useFontdueUrl } from '../UrlContext.js';
11
+ const query = (_CustomerSignInElementQuery.hash && _CustomerSignInElementQuery.hash !== "e7d4a8fb8d19581d6bc36f29090230af" && console.error("The definition of 'CustomerSignInElementQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CustomerSignInElementQuery);
12
+
13
+ /** Dispatched on `window` once the customer is signed in; `detail.customer`. */
14
+ export const SIGNED_IN_EVENT = 'fontdue:customer-signed-in';
15
+ /** Dispatched on `window` when the customer clicks the element's sign-out button. */
16
+ export const SIGNED_OUT_EVENT = 'fontdue:customer-signed-out';
17
+ /**
18
+ * `<fontdue-customer-sign-in>`: the script-tag wrapper around `CustomerSignIn`.
19
+ *
20
+ * A page has no React of its own to read `useCurrentCustomer()` with, so the
21
+ * element shows the signed-in state itself – who is signed in and a way out –
22
+ * and tells the page about both changes with window events, as `setConsent`
23
+ * does with `fontdue:consent`.
24
+ *
25
+ * Not in the npm package's `exports`: sign-in is script-tag only for now.
26
+ */
27
+ export default function CustomerSignInElement(_ref) {
28
+ let {
29
+ signOutLabel = 'Sign out',
30
+ ...props
31
+ } = _ref;
32
+ const url = useFontdueUrl();
33
+ // The script tag never renders on a server, so reading the store during
34
+ // render can't cause a hydration mismatch – and it means a returning
35
+ // customer never sees the form flash before their session is read.
36
+ const accessToken = useSyncExternalStore(subscribeToTokens, () => {
37
+ var _getTokens;
38
+ return ((_getTokens = getTokens(url)) === null || _getTokens === void 0 ? void 0 : _getTokens.accessToken) ?? null;
39
+ }, () => null);
40
+ // The customer `verifyLoginCode` just returned, tied to the token it came
41
+ // with, so the signed-in state shows at once instead of after a lookup. Any
42
+ // other session – a page load, a sign-in in another element, a renewed
43
+ // token – is looked up.
44
+ const [justSignedIn, setJustSignedIn] = useState(null);
45
+ const handleSignedIn = useCallback(customer => {
46
+ if (customer) {
47
+ var _getTokens2;
48
+ setJustSignedIn({
49
+ accessToken: (_getTokens2 = getTokens(url)) === null || _getTokens2 === void 0 ? void 0 : _getTokens2.accessToken,
50
+ customer
51
+ });
52
+ }
53
+ window.dispatchEvent(new CustomEvent(SIGNED_IN_EVENT, {
54
+ detail: {
55
+ customer
56
+ }
57
+ }));
58
+ }, [url]);
59
+ const signOut = useCallback(() => {
60
+ clearTokens(url);
61
+ setJustSignedIn(null);
62
+ window.dispatchEvent(new CustomEvent(SIGNED_OUT_EVENT));
63
+ }, [url]);
64
+ const form = /*#__PURE__*/React.createElement(CustomerSignIn, _extends({}, props, {
65
+ onSignedIn: handleSignedIn
66
+ }));
67
+ if (accessToken == null) return form;
68
+ if (justSignedIn && justSignedIn.accessToken === accessToken) {
69
+ return /*#__PURE__*/React.createElement(SignedIn, {
70
+ customer: justSignedIn.customer,
71
+ signOut: signOut,
72
+ signOutLabel: signOutLabel
73
+ });
74
+ }
75
+
76
+ // A lookup that fails leaves the visitor able to sign in again rather than
77
+ // facing an empty element.
78
+ return /*#__PURE__*/React.createElement(ErrorBoundary, {
79
+ fallback: form,
80
+ resetKeys: [accessToken]
81
+ }, /*#__PURE__*/React.createElement(Suspense, {
82
+ fallback: null
83
+ }, /*#__PURE__*/React.createElement(SignedInLookup, {
84
+ key: accessToken,
85
+ form: form,
86
+ signOut: signOut,
87
+ signOutLabel: signOutLabel
88
+ })));
89
+ }
90
+ function SignedInLookup(_ref2) {
91
+ var _data$viewer;
92
+ let {
93
+ form,
94
+ signOut,
95
+ signOutLabel
96
+ } = _ref2;
97
+ // network-only: who is signed in *now*, never a store hit left over from the
98
+ // previous customer.
99
+ const data = useLazyLoadQuery(query, {}, {
100
+ fetchPolicy: 'network-only'
101
+ });
102
+ const customer = (_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : _data$viewer.currentCustomer;
103
+
104
+ // A token the server no longer honours is cleared by the network layer,
105
+ // which re-renders the element signed out. A null here without that is a
106
+ // session that names nobody; offer the form.
107
+ if (!customer) return form;
108
+ return /*#__PURE__*/React.createElement(SignedIn, {
109
+ customer: customer,
110
+ signOut: signOut,
111
+ signOutLabel: signOutLabel
112
+ });
113
+ }
114
+ function SignedIn(_ref3) {
115
+ let {
116
+ customer,
117
+ signOut,
118
+ signOutLabel
119
+ } = _ref3;
120
+ return /*#__PURE__*/React.createElement("div", {
121
+ className: "customer-sign-in",
122
+ "data-step": "signed-in"
123
+ }, /*#__PURE__*/React.createElement("p", {
124
+ className: "customer-sign-in__intro"
125
+ }, "Signed in as ", customer.email ?? customer.name, "."), /*#__PURE__*/React.createElement("div", {
126
+ className: "customer-sign-in__actions"
127
+ }, /*#__PURE__*/React.createElement("button", {
128
+ className: "customer-sign-in__back",
129
+ type: "button",
130
+ onClick: signOut
131
+ }, signOutLabel)));
132
+ }
@@ -54,9 +54,11 @@ function CustomerSignInFields(_ref) {
54
54
  email
55
55
  }
56
56
  },
57
- onCompleted: res => {
57
+ onCompleted: (res, errors) => {
58
58
  var _res$requestLoginCode;
59
- if ((_res$requestLoginCode = res.requestLoginCode) !== null && _res$requestLoginCode !== void 0 && _res$requestLoginCode.success) {
59
+ if (errors && errors.length > 0) {
60
+ setError(errors[0].message);
61
+ } else if ((_res$requestLoginCode = res.requestLoginCode) !== null && _res$requestLoginCode !== void 0 && _res$requestLoginCode.success) {
60
62
  setStep('code');
61
63
  } else {
62
64
  // The only unsuccessful answer is the rate limit, and it is about the
@@ -25,6 +25,8 @@ const customElementMap = {
25
25
  'fontdue-cookie-notification': /*#__PURE__*/lazy(() => retryImport(() => import('../CookieNotification/index.js'))),
26
26
  'fontdue-font-families': /*#__PURE__*/lazy(() => retryImport(() => import('../FontFamilies/index.js'))),
27
27
  'fontdue-customer-login-form': /*#__PURE__*/lazy(() => retryImport(() => import('../CustomerLoginForm/index.js'))),
28
+ // Script tag only: CustomerSignIn isn't in the npm package's `exports` yet.
29
+ 'fontdue-customer-sign-in': /*#__PURE__*/lazy(() => retryImport(() => import('../CustomerSignIn/CustomerSignInElement.js'))),
28
30
  'fontdue-newsletter-signup': /*#__PURE__*/lazy(() => retryImport(() => import('../NewsletterSignup/NewsletterSignupElement.js'))),
29
31
  'fontdue-node-password-form': /*#__PURE__*/lazy(() => retryImport(() => import('../NodePasswordForm/NodePasswordFormElement.js'))),
30
32
  // @ts-ignore
@@ -1,17 +1,15 @@
1
1
  import _productState_Query from "../../__generated__/productState_Query.graphql.js";
2
2
  import _productState_fontPack from "../../__generated__/productState_fontPack.graphql.js";
3
3
  import _productState_node from "../../__generated__/productState_node.graphql.js";
4
- import _productState_collectionBundle from "../../__generated__/productState_collectionBundle.graphql.js";
5
4
  import _productState_bundle from "../../__generated__/productState_bundle.graphql.js";
6
5
  import _productState_collection from "../../__generated__/productState_collection.graphql.js";
7
6
  import _productState_sku from "../../__generated__/productState_sku.graphql.js";
8
7
  import { fetchQuery, graphql } from 'react-relay';
9
8
  import { notEmpty } from '../../utils.js';
10
9
  _productState_sku.hash && _productState_sku.hash !== "2cee6ddddc85699f0c90e2607c0634ef" && console.error("The definition of 'productState_sku' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_sku;
11
- _productState_collection.hash && _productState_collection.hash !== "90e56933f956c6773c0958284de81884" && console.error("The definition of 'productState_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_collection;
10
+ _productState_collection.hash && _productState_collection.hash !== "5ad23bbf68581f27e769ebb0cda70f8e" && console.error("The definition of 'productState_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_collection;
12
11
  _productState_bundle.hash && _productState_bundle.hash !== "fa38bbc9f2062a727da4f5062b362054" && console.error("The definition of 'productState_bundle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_bundle;
13
- _productState_collectionBundle.hash && _productState_collectionBundle.hash !== "6744aa8f57d14b0111a0c49f488d7b03" && console.error("The definition of 'productState_collectionBundle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_collectionBundle;
14
- _productState_node.hash && _productState_node.hash !== "a1e172db45ca550a254819d877497d12" && console.error("The definition of 'productState_node' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_node;
12
+ _productState_node.hash && _productState_node.hash !== "aab1ef07e0192a0d22a4c300084d9d05" && console.error("The definition of 'productState_node' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_node;
15
13
 
16
14
  // A pack is one SKU that grants every style in `fontStyles`. Its collection
17
15
  // members (a family or a superfamily with its families) are fetched in the
@@ -41,37 +39,21 @@ const allSkus = function (collection) {
41
39
  var _collection$bundles;
42
40
  bundleSkus = (collection === null || collection === void 0 ? void 0 : (_collection$bundles = collection.bundles) === null || _collection$bundles === void 0 ? void 0 : _collection$bundles.flatMap(bundle => allSkus(bundle, includeCollections, includeFontStyles))) ?? [];
43
41
  }
44
- let collectionBundleSkus = [];
45
- if ('collectionBundles' in collection && collection.collectionBundles) {
46
- collectionBundleSkus = collection.collectionBundles.flatMap(collBundle => allSkus(collBundle, includeCollections, includeFontStyles));
47
- }
48
-
49
- // For collection bundles, get SKUs from member collections
50
- let memberCollectionSkus = [];
51
- if ('memberCollections' in collection && collection.memberCollections) {
52
- memberCollectionSkus = collection.memberCollections.flatMap(member => {
53
- var _member$fontStyles;
54
- return [includeCollections ? member.sku : null, ...(includeFontStyles ? ((_member$fontStyles = member.fontStyles) === null || _member$fontStyles === void 0 ? void 0 : _member$fontStyles.map(style => style.sku)) ?? [] : [])];
55
- }).filter(notEmpty);
56
- }
57
42
  let childrenSkus = [];
58
43
  if ('children' in collection) {
59
44
  var _collection$children;
60
45
  childrenSkus = ((_collection$children = collection.children) === null || _collection$children === void 0 ? void 0 : _collection$children.flatMap(child => allSkus(child, includeCollections, includeFontStyles))) ?? [];
61
46
  }
62
- return [includeCollections ? collection === null || collection === void 0 ? void 0 : collection.sku : null, ...(includeFontStyles && 'fontStyles' in collection ? (collection === null || collection === void 0 ? void 0 : (_collection$fontStyle = collection.fontStyles) === null || _collection$fontStyle === void 0 ? void 0 : _collection$fontStyle.map(style => style.sku)) ?? [] : []), ...bundleSkus, ...collectionBundleSkus, ...memberCollectionSkus, ...childrenSkus].filter(notEmpty);
47
+ return [includeCollections ? collection === null || collection === void 0 ? void 0 : collection.sku : null, ...(includeFontStyles && 'fontStyles' in collection ? (collection === null || collection === void 0 ? void 0 : (_collection$fontStyle = collection.fontStyles) === null || _collection$fontStyle === void 0 ? void 0 : _collection$fontStyle.map(style => style.sku)) ?? [] : []), ...bundleSkus, ...childrenSkus].filter(notEmpty);
63
48
  };
64
49
 
65
50
  // The styles each SKU grants, by SKU id: a style's SKU grants that style, a
66
- // bundle its styles, a family its styles, a superfamily its families' styles
67
- // and a collection bundle its member collections' styles. This is all the
51
+ // bundle its styles (a whole-family member expanded to the family's styles), a
52
+ // family its styles and a superfamily its families' styles. This is all the
68
53
  // selection needs to price any combination of them (see selection.ts).
69
54
 
70
55
  const styleIdsOf = collection => {
71
56
  var _collection$fontStyle2, _collection$children2;
72
- if ('memberCollections' in collection && collection.memberCollections) {
73
- return collection.memberCollections.flatMap(member => member.fontStyles.map(s => s.id));
74
- }
75
57
  const own = 'fontStyles' in collection ? ((_collection$fontStyle2 = collection.fontStyles) === null || _collection$fontStyle2 === void 0 ? void 0 : _collection$fontStyle2.map(s => s.id)) ?? [] : [];
76
58
  const children = 'children' in collection ? ((_collection$children2 = collection.children) === null || _collection$children2 === void 0 ? void 0 : _collection$children2.flatMap(child => {
77
59
  var _child$fontStyles;
@@ -83,8 +65,7 @@ const flattenSkuStyleIds = collection => {
83
65
  var _collection$sku;
84
66
  const result = {};
85
67
  const add = (skuId, styleIds) => {
86
- // A collection that returns no styles of its own (a superfamily listed as
87
- // a collection bundle member) says nothing about what it grants.
68
+ // A SKU that lists no styles says nothing about what it grants.
88
69
  if (skuId && styleIds.length > 0) result[skuId] = styleIds;
89
70
  };
90
71
  const addStyles = fontStyles => {
@@ -95,14 +76,7 @@ const flattenSkuStyleIds = collection => {
95
76
  };
96
77
  add((_collection$sku = collection.sku) === null || _collection$sku === void 0 ? void 0 : _collection$sku.id, styleIdsOf(collection));
97
78
  if ('fontStyles' in collection) addStyles(collection.fontStyles);
98
- if ('memberCollections' in collection) {
99
- for (const member of collection.memberCollections ?? []) {
100
- var _member$sku;
101
- add((_member$sku = member.sku) === null || _member$sku === void 0 ? void 0 : _member$sku.id, member.fontStyles.map(s => s.id));
102
- addStyles(member.fontStyles);
103
- }
104
- }
105
- const nested = [...('bundles' in collection ? collection.bundles ?? [] : []), ...('collectionBundles' in collection ? collection.collectionBundles ?? [] : []), ...('children' in collection ? collection.children ?? [] : [])];
79
+ const nested = [...('bundles' in collection ? collection.bundles ?? [] : []), ...('children' in collection ? collection.children ?? [] : [])];
106
80
  for (const child of nested) Object.assign(result, flattenSkuStyleIds(child));
107
81
  return result;
108
82
  };
@@ -40,18 +40,22 @@ export default function StoreModalFamily(_ref) {
40
40
  onSelect,
41
41
  title
42
42
  } = _ref;
43
- const collection = useFragment((_StoreModalFamily_collection.hash && _StoreModalFamily_collection.hash !== "f136cd0050f0f75f561221fac75feb85" && console.error("The definition of 'StoreModalFamily_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalFamily_collection), collectionKey);
43
+ const collection = useFragment((_StoreModalFamily_collection.hash && _StoreModalFamily_collection.hash !== "9f2a8cd937ebe2c06f2bf771d38de985" && console.error("The definition of 'StoreModalFamily_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalFamily_collection), collectionKey);
44
44
  const fontStyles = useMemo(() => stylesGrouped(collection.fontStyles), [collection]);
45
- if ((fontStyles === null || fontStyles === void 0 ? void 0 : fontStyles.length) === 0 && collection.collectionType === 'superfamily' && !collection.sku) return null;
45
+ // An unlisted bundle gets no button, but its price still applies as an
46
+ // upgrade: the product state reads every bundle.
47
+ const listedBundles = (_collection$bundles = collection.bundles) === null || _collection$bundles === void 0 ? void 0 : _collection$bundles.filter(bundle => bundle.visibility !== 'UNLISTED');
48
+
49
+ // A superfamily sold only through its families has nothing of its own to
50
+ // show – unless one of its bundles, across its families, has a button.
51
+ if ((fontStyles === null || fontStyles === void 0 ? void 0 : fontStyles.length) === 0 && collection.collectionType === 'superfamily' && !collection.sku && !(listedBundles !== null && listedBundles !== void 0 && listedBundles.length)) return null;
46
52
  return /*#__PURE__*/React.createElement(FamilyElement, null, {
47
53
  title,
48
54
  familyButton: /*#__PURE__*/React.createElement(FamilyButton, {
49
55
  collection: collection,
50
56
  onSelectSku: onSelect
51
57
  }),
52
- // An unlisted bundle gets no button, but its price still applies as an
53
- // upgrade: the product state reads every bundle.
54
- bundles: collection === null || collection === void 0 ? void 0 : (_collection$bundles = collection.bundles) === null || _collection$bundles === void 0 ? void 0 : _collection$bundles.filter(bundle => bundle.visibility !== 'UNLISTED').map(bundle => /*#__PURE__*/React.createElement(StoreModalBundleButton, {
58
+ bundles: listedBundles === null || listedBundles === void 0 ? void 0 : listedBundles.map(bundle => /*#__PURE__*/React.createElement(StoreModalBundleButton, {
55
59
  key: bundle.id,
56
60
  bundle: bundle,
57
61
  onSelectSku: onSelect
@@ -1,11 +1,13 @@
1
1
  import _StripeProviderCreateCheckoutSessionMutation from "../../__generated__/StripeProviderCreateCheckoutSessionMutation.graphql.js";
2
2
  import _StripeProvider_viewer from "../../__generated__/StripeProvider_viewer.graphql.js";
3
- import React, { useCallback, useContext, useRef } from 'react';
3
+ import React, { useCallback, useContext, useRef, useState } from 'react';
4
4
  import { commitMutation, graphql, useFragment, useRelayEnvironment } from 'react-relay';
5
5
  import { Elements, EmbeddedCheckoutProvider } from '@stripe/react-stripe-js';
6
6
  import { loadStripe } from '@stripe/stripe-js';
7
7
  import { parseCustomProperties } from '../../utils.js';
8
8
  import ConfigContext from '../ConfigContext.js';
9
+ import StoreModalPageContainer from '../elements/StoreModalPageContainer/index.js';
10
+ const CHECKOUT_ERROR = 'We couldn’t start the checkout – please try again';
9
11
  const StripeProvider = _ref => {
10
12
  var _data$currentOrder, _data$settings, _data$settings$uiFont, _data$settings$uiFont2, _data$settings$uiFont3, _data$settings2, _data$currentOrder2, _data$currentOrder2$s, _data$themeConfig;
11
13
  let {
@@ -26,6 +28,7 @@ const StripeProvider = _ref => {
26
28
  const {
27
29
  stripe: config
28
30
  } = useContext(ConfigContext);
31
+ const [checkoutError, setCheckoutError] = useState(null);
29
32
  let customFontSource;
30
33
  const woffSrc = (_data$settings = data.settings) === null || _data$settings === void 0 ? void 0 : (_data$settings$uiFont = _data$settings.uiFontStyle) === null || _data$settings$uiFont === void 0 ? void 0 : (_data$settings$uiFont2 = _data$settings$uiFont.webfontSources) === null || _data$settings$uiFont2 === void 0 ? void 0 : (_data$settings$uiFont3 = _data$settings$uiFont2.find(source => (source === null || source === void 0 ? void 0 : source.format) === 'woff2')) === null || _data$settings$uiFont3 === void 0 ? void 0 : _data$settings$uiFont3.url;
31
34
  let fontFamily = (_data$settings2 = data.settings) !== null && _data$settings2 !== void 0 && _data$settings2.uiFontStyle ? `${data.settings.uiFontStyle.cssFamily} ${data.settings.uiFontStyle.name}` : undefined;
@@ -50,16 +53,22 @@ const StripeProvider = _ref => {
50
53
  commitMutation(environment, {
51
54
  mutation: (_StripeProviderCreateCheckoutSessionMutation.hash && _StripeProviderCreateCheckoutSessionMutation.hash !== "2ca9a7fcfed1292117e3cd6d0d95cca6" && console.error("The definition of 'StripeProviderCreateCheckoutSessionMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StripeProviderCreateCheckoutSessionMutation),
52
55
  variables: {},
53
- onCompleted: response => {
56
+ onCompleted: (response, errors) => {
54
57
  var _response$createOrder, _response$createOrder2, _response$createOrder3;
55
58
  const clientSecret = (_response$createOrder = response.createOrderCheckoutSession) === null || _response$createOrder === void 0 ? void 0 : (_response$createOrder2 = _response$createOrder.order) === null || _response$createOrder2 === void 0 ? void 0 : (_response$createOrder3 = _response$createOrder2.stripeCheckoutSession) === null || _response$createOrder3 === void 0 ? void 0 : _response$createOrder3.clientSecret;
56
59
  if (clientSecret) {
57
60
  resolve(clientSecret);
58
61
  } else {
62
+ var _errors$;
63
+ // Stripe refused the session (e.g. a total under its minimum
64
+ // charge): show its reason, which the embedded checkout would
65
+ // otherwise swallow into a generic error.
66
+ setCheckoutError((errors === null || errors === void 0 ? void 0 : (_errors$ = errors[0]) === null || _errors$ === void 0 ? void 0 : _errors$.message) ?? CHECKOUT_ERROR);
59
67
  reject("Didn't find client secret");
60
68
  }
61
69
  },
62
70
  onError: error => {
71
+ setCheckoutError(CHECKOUT_ERROR);
63
72
  reject(`Error creating checkout session: ${error.message}`);
64
73
  }
65
74
  });
@@ -147,6 +156,14 @@ const StripeProvider = _ref => {
147
156
  syncAddressCheckbox: 'none'
148
157
  };
149
158
  if (providerType === 'checkout') {
159
+ if (checkoutError) {
160
+ return /*#__PURE__*/React.createElement(StoreModalPageContainer, null, {
161
+ body: /*#__PURE__*/React.createElement("div", {
162
+ className: "store-modal__cart__error",
163
+ role: "alert"
164
+ }, checkoutError)
165
+ });
166
+ }
150
167
  return /*#__PURE__*/React.createElement(EmbeddedCheckoutProvider, {
151
168
  stripe: stripePromiseRef.current,
152
169
  options: {
@@ -1,6 +1,7 @@
1
- import React from 'react';
1
+ import React, { useRef } from 'react';
2
2
  import { compareVariableSettings, variableInstanceSettings } from '../../utils.js';
3
3
  import Select from '../Select/index.js';
4
+ import usePrefetchWhenNear from './usePrefetchWhenNear.js';
4
5
  const VARIABLE_AXES = 'VARIABLE_AXES';
5
6
  const LOADING = '__loading__';
6
7
  const TypeTesterStyleSelect = _ref => {
@@ -19,6 +20,8 @@ const TypeTesterStyleSelect = _ref => {
19
20
  onSelectFamilyId,
20
21
  includeVariableAxesOption
21
22
  } = _ref;
23
+ const containerRef = useRef(null);
24
+ usePrefetchWhenNear(containerRef, onOpen);
22
25
  const handleSelectFontStyle = selection => {
23
26
  if (selection === VARIABLE_AXES) {
24
27
  onSelectVariableAxes();
@@ -90,7 +93,9 @@ const TypeTesterStyleSelect = _ref => {
90
93
  cb(e.target.value);
91
94
  };
92
95
  return /*#__PURE__*/React.createElement("div", {
96
+ ref: containerRef,
93
97
  className: "type-tester-select",
98
+ onPointerEnter: () => onOpen === null || onOpen === void 0 ? void 0 : onOpen(),
94
99
  "data-loading": loading || undefined,
95
100
  "aria-busy": loading || undefined,
96
101
  style: {
@@ -0,0 +1,14 @@
1
+ export declare const PREFETCH_ROOT_MARGIN = "300px";
2
+ export declare const PREFETCH_IDLE_TIMEOUT = 2000;
3
+ /**
4
+ * Calls `prefetch` once, at idle time, after the element first comes near the
5
+ * viewport. Native `<select>` menus snapshot their options when they open (in
6
+ * Safari the open menu never updates), so data a dropdown needs has to be
7
+ * there before the first click – fetching on mousedown is too late. Waiting
8
+ * for idle keeps the request off the initial render, and waiting for the
9
+ * viewport keeps a long page of testers from fetching for every one at once.
10
+ *
11
+ * const ref = useRef<HTMLDivElement>(null);
12
+ * usePrefetchWhenNear(ref, loadOptions);
13
+ */
14
+ export default function usePrefetchWhenNear(ref: React.RefObject<Element | null>, prefetch: (() => void) | undefined): void;
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ // How far outside the viewport a tester counts as "near".
4
+ export const PREFETCH_ROOT_MARGIN = '300px';
5
+ // Upper bound on waiting for the browser to go idle.
6
+ export const PREFETCH_IDLE_TIMEOUT = 2000;
7
+ function onIdle(cb) {
8
+ if (typeof window.requestIdleCallback === 'function') {
9
+ const id = window.requestIdleCallback(cb, {
10
+ timeout: PREFETCH_IDLE_TIMEOUT
11
+ });
12
+ return {
13
+ cancel: () => window.cancelIdleCallback(id)
14
+ };
15
+ }
16
+ // Safari has no requestIdleCallback.
17
+ const id = window.setTimeout(cb, 200);
18
+ return {
19
+ cancel: () => window.clearTimeout(id)
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Calls `prefetch` once, at idle time, after the element first comes near the
25
+ * viewport. Native `<select>` menus snapshot their options when they open (in
26
+ * Safari the open menu never updates), so data a dropdown needs has to be
27
+ * there before the first click – fetching on mousedown is too late. Waiting
28
+ * for idle keeps the request off the initial render, and waiting for the
29
+ * viewport keeps a long page of testers from fetching for every one at once.
30
+ *
31
+ * const ref = useRef<HTMLDivElement>(null);
32
+ * usePrefetchWhenNear(ref, loadOptions);
33
+ */
34
+ export default function usePrefetchWhenNear(ref, prefetch) {
35
+ const prefetchRef = useRef(prefetch);
36
+ prefetchRef.current = prefetch;
37
+ useEffect(() => {
38
+ const el = ref.current;
39
+ if (!el) return;
40
+ let idle = null;
41
+ const schedule = () => {
42
+ idle ??= onIdle(() => {
43
+ var _prefetchRef$current;
44
+ return (_prefetchRef$current = prefetchRef.current) === null || _prefetchRef$current === void 0 ? void 0 : _prefetchRef$current.call(prefetchRef);
45
+ });
46
+ };
47
+ if (typeof IntersectionObserver !== 'function') {
48
+ schedule();
49
+ return () => {
50
+ var _idle;
51
+ return (_idle = idle) === null || _idle === void 0 ? void 0 : _idle.cancel();
52
+ };
53
+ }
54
+ const observer = new IntersectionObserver(entries => {
55
+ if (entries.some(entry => entry.isIntersecting)) {
56
+ observer.disconnect();
57
+ schedule();
58
+ }
59
+ }, {
60
+ rootMargin: PREFETCH_ROOT_MARGIN
61
+ });
62
+ observer.observe(el);
63
+ return () => {
64
+ var _idle2;
65
+ observer.disconnect();
66
+ (_idle2 = idle) === null || _idle2 === void 0 ? void 0 : _idle2.cancel();
67
+ };
68
+ }, [ref]);
69
+ }
package/dist/fontdue.css CHANGED
@@ -2136,6 +2136,14 @@ input:checked + .checkbox__icon {
2136
2136
  font-size: 60px;
2137
2137
  }
2138
2138
 
2139
+ .store-modal__index-item__description:first-child {
2140
+ grid-column: 1/-2;
2141
+ }
2142
+
2143
+ .store-modal__index-item__buy {
2144
+ grid-column: -2/-1;
2145
+ }
2146
+
2139
2147
  .store-modal__index-item__name {
2140
2148
  font-size: 20px;
2141
2149
  line-height: 30px;
@@ -4007,6 +4015,7 @@ input:disabled + .fontdue-admin-toolbar__switch {
4007
4015
 
4008
4016
  fontdue-test-fonts-form,
4009
4017
  fontdue-newsletter-signup,
4010
- fontdue-customer-login-form {
4018
+ fontdue-customer-login-form,
4019
+ fontdue-customer-sign-in {
4011
4020
  font-size: 1rem;
4012
4021
  }
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@
8
8
  // (Fontage.ClientVersions) to spot sites that need to upgrade before a
9
9
  // feature that depends on a newer client can work for them.
10
10
  export const CLIENT_VERSION_HEADER = 'fontdue-client-version';
11
- export const version = "3.12.0";
11
+ export const version = "3.13.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",
@@ -1,41 +0,0 @@
1
- /**
2
- * @generated SignedSource<<c1a8446af3677d58e3851338c54cf16e>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
- import { ReaderFragment } from 'relay-runtime';
7
- import { FragmentRefs } from "relay-runtime";
8
- export type productState_collectionBundle$data = {
9
- readonly id: string;
10
- readonly memberCollections: ReadonlyArray<{
11
- readonly fontStyles: ReadonlyArray<{
12
- readonly id: string;
13
- readonly sku: {
14
- readonly id: string;
15
- readonly price: {
16
- readonly amount: number;
17
- };
18
- } | null;
19
- }>;
20
- readonly id: string;
21
- readonly sku: {
22
- readonly id: string;
23
- readonly price: {
24
- readonly amount: number;
25
- };
26
- } | null;
27
- }> | null;
28
- readonly name: string;
29
- readonly sku: {
30
- readonly id: string;
31
- readonly price: {
32
- readonly amount: number;
33
- };
34
- } | null;
35
- };
36
- export type productState_collectionBundle$key = {
37
- readonly " $data"?: productState_collectionBundle$data;
38
- readonly " $fragmentSpreads": FragmentRefs<"productState_collectionBundle">;
39
- };
40
- declare const node: ReaderFragment;
41
- export default node;
@@ -1,81 +0,0 @@
1
- /**
2
- * @generated SignedSource<<c1a8446af3677d58e3851338c54cf16e>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
-
7
- /* tslint:disable */
8
- /* eslint-disable */
9
- // @ts-nocheck
10
-
11
- const node = function () {
12
- var v0 = {
13
- "alias": null,
14
- "args": null,
15
- "kind": "ScalarField",
16
- "name": "id",
17
- "storageKey": null
18
- },
19
- v1 = {
20
- "alias": null,
21
- "args": null,
22
- "concreteType": "Sku",
23
- "kind": "LinkedField",
24
- "name": "sku",
25
- "plural": false,
26
- "selections": [v0 /*: any*/, {
27
- "alias": null,
28
- "args": null,
29
- "concreteType": "Money",
30
- "kind": "LinkedField",
31
- "name": "price",
32
- "plural": false,
33
- "selections": [{
34
- "alias": null,
35
- "args": null,
36
- "kind": "ScalarField",
37
- "name": "amount",
38
- "storageKey": null
39
- }],
40
- "storageKey": null
41
- }],
42
- "storageKey": null
43
- };
44
- return {
45
- "argumentDefinitions": [],
46
- "kind": "Fragment",
47
- "metadata": {
48
- "mask": false
49
- },
50
- "name": "productState_collectionBundle",
51
- "selections": [v0 /*: any*/, {
52
- "alias": null,
53
- "args": null,
54
- "kind": "ScalarField",
55
- "name": "name",
56
- "storageKey": null
57
- }, v1 /*: any*/, {
58
- "alias": null,
59
- "args": null,
60
- "concreteType": "FontCollection",
61
- "kind": "LinkedField",
62
- "name": "memberCollections",
63
- "plural": true,
64
- "selections": [v0 /*: any*/, v1 /*: any*/, {
65
- "alias": null,
66
- "args": null,
67
- "concreteType": "FontStyle",
68
- "kind": "LinkedField",
69
- "name": "fontStyles",
70
- "plural": true,
71
- "selections": [v0 /*: any*/, v1 /*: any*/],
72
- "storageKey": null
73
- }],
74
- "storageKey": null
75
- }],
76
- "type": "CollectionBundle",
77
- "abstractKey": null
78
- };
79
- }();
80
- node.hash = "6744aa8f57d14b0111a0c49f488d7b03";
81
- export default node;