fontdue-js 3.7.0 → 3.8.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 (123) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +5 -4
  3. package/dist/__generated__/AddToCartBannerQuery.graphql.d.ts +1 -1
  4. package/dist/__generated__/AddToCartBannerQuery.graphql.js +16 -10
  5. package/dist/__generated__/AddToCartBanner_item.graphql.d.ts +4 -1
  6. package/dist/__generated__/AddToCartBanner_item.graphql.js +7 -2
  7. package/dist/__generated__/AddToCartBanner_order.graphql.d.ts +4 -1
  8. package/dist/__generated__/AddToCartBanner_order.graphql.js +6 -1
  9. package/dist/__generated__/BuyButtonIDQuery.graphql.d.ts +3 -3
  10. package/dist/__generated__/BuyButtonIDQuery.graphql.js +31 -16
  11. package/dist/__generated__/BuyButtonPackIDQuery.graphql.d.ts +21 -0
  12. package/dist/__generated__/BuyButtonPackIDQuery.graphql.js +129 -0
  13. package/dist/__generated__/BuyButtonPackSlugQuery.graphql.d.ts +25 -0
  14. package/dist/__generated__/BuyButtonPackSlugQuery.graphql.js +167 -0
  15. package/dist/__generated__/BuyButtonSlugQuery.graphql.d.ts +3 -3
  16. package/dist/__generated__/BuyButtonSlugQuery.graphql.js +45 -17
  17. package/dist/__generated__/BuyButton_product.graphql.d.ts +27 -0
  18. package/dist/__generated__/BuyButton_product.graphql.js +72 -0
  19. package/dist/__generated__/CartItemProduct_product.graphql.d.ts +1 -1
  20. package/dist/__generated__/CartItemProduct_product.graphql.js +7 -2
  21. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
  22. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +26 -20
  23. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
  24. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +26 -20
  25. package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
  26. package/dist/__generated__/CartOrderUpdateMutation.graphql.js +26 -20
  27. package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
  28. package/dist/__generated__/CartQuery.graphql.js +26 -20
  29. package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
  30. package/dist/__generated__/CartStateUpdateMutation.graphql.js +26 -20
  31. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
  32. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +26 -20
  33. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
  34. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +26 -20
  35. package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
  36. package/dist/__generated__/CollectionAa_Query.graphql.js +20 -14
  37. package/dist/__generated__/CollectionAa_product.graphql.d.ts +7 -1
  38. package/dist/__generated__/CollectionAa_product.graphql.js +22 -16
  39. package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.d.ts +3 -3
  40. package/dist/__generated__/NodePasswordFormElementIDQuery.graphql.js +9 -9
  41. package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.d.ts +25 -0
  42. package/dist/__generated__/NodePasswordFormElementPackSlugQuery.graphql.js +108 -0
  43. package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
  44. package/dist/__generated__/PrecartAddToCartMutation.graphql.js +32 -26
  45. package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
  46. package/dist/__generated__/StoreModalCartQuery.graphql.js +37 -31
  47. package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.d.ts +23 -0
  48. package/dist/__generated__/StoreModalIndexPackItem_fontPack.graphql.js +58 -0
  49. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +9 -1
  50. package/dist/__generated__/StoreModalIndexQuery.graphql.js +174 -110
  51. package/dist/__generated__/StoreModalPackButton_fontPack.graphql.d.ts +33 -0
  52. package/dist/__generated__/StoreModalPackButton_fontPack.graphql.js +119 -0
  53. package/dist/__generated__/StoreModalPack_fontPack.graphql.d.ts +45 -0
  54. package/dist/__generated__/StoreModalPack_fontPack.graphql.js +150 -0
  55. package/dist/__generated__/StoreModalProductContent_collection.graphql.d.ts +3 -2
  56. package/dist/__generated__/StoreModalProductContent_collection.graphql.js +36 -19
  57. package/dist/__generated__/{StoreModalProductLicenseSelection_collection.graphql.d.ts → StoreModalProductLicenseSelection_licenseable.graphql.d.ts} +6 -6
  58. package/dist/__generated__/{StoreModalProductLicenseSelection_collection.graphql.js → StoreModalProductLicenseSelection_licenseable.graphql.js} +5 -5
  59. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  60. package/dist/__generated__/StoreModalProductQuery.graphql.js +159 -103
  61. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  62. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +184 -128
  63. package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +5 -1
  64. package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +248 -171
  65. package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.d.ts +1 -1
  66. package/dist/__generated__/StoreModalProductSummaryRefetchQuery.graphql.js +9 -4
  67. package/dist/__generated__/StoreModalProductSummary_viewer.graphql.d.ts +7 -1
  68. package/dist/__generated__/StoreModalProductSummary_viewer.graphql.js +7 -2
  69. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
  70. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +26 -20
  71. package/dist/__generated__/productState_Query.graphql.d.ts +99 -1
  72. package/dist/__generated__/productState_Query.graphql.js +43 -4
  73. package/dist/__generated__/productState_fontPack.graphql.d.ts +111 -0
  74. package/dist/__generated__/productState_fontPack.graphql.js +117 -0
  75. package/dist/__tests__/bundleSelectionOrder.test.js +2 -0
  76. package/dist/__tests__/collectionBundleSelection.test.js +2 -0
  77. package/dist/__tests__/fontPackSelection.test.js +435 -0
  78. package/dist/__tests__/licenseExclusions.test.js +2 -0
  79. package/dist/__tests__/storeModalPackButton.test.js +102 -0
  80. package/dist/__tests__/storeModalPackUnlisted.test.js +112 -0
  81. package/dist/__tests__/storeModalRoutes.test.js +9 -0
  82. package/dist/components/AddToCartBanner/index.js +1 -1
  83. package/dist/components/BuyButton/index.d.ts +29 -9
  84. package/dist/components/BuyButton/index.js +123 -26
  85. package/dist/components/BuyButton/index.server.d.ts +4 -2
  86. package/dist/components/BuyButton/index.server.js +24 -3
  87. package/dist/components/Cart/CartItem/CartItemProduct.js +3 -3
  88. package/dist/components/CollectionAa/index.js +2 -2
  89. package/dist/components/NodePasswordForm/NodePasswordFormElement.d.ts +1 -1
  90. package/dist/components/NodePasswordForm/NodePasswordFormElement.js +45 -21
  91. package/dist/components/NodePasswordForm/index.d.ts +7 -4
  92. package/dist/components/NodePasswordForm/index.js +6 -6
  93. package/dist/components/Root/productState.js +57 -7
  94. package/dist/components/StoreModal/StoreModalFamily.d.ts +7 -0
  95. package/dist/components/StoreModal/StoreModalFamily.js +7 -5
  96. package/dist/components/StoreModal/StoreModalIndex.js +13 -3
  97. package/dist/components/StoreModal/StoreModalIndexEntry.d.ts +13 -0
  98. package/dist/components/StoreModal/StoreModalIndexEntry.js +43 -0
  99. package/dist/components/StoreModal/StoreModalIndexItem.js +7 -30
  100. package/dist/components/StoreModal/StoreModalIndexPackItem.d.ts +7 -0
  101. package/dist/components/StoreModal/StoreModalIndexPackItem.js +21 -0
  102. package/dist/components/StoreModal/StoreModalPack.d.ts +9 -0
  103. package/dist/components/StoreModal/StoreModalPack.js +79 -0
  104. package/dist/components/StoreModal/StoreModalPackButton.d.ts +8 -0
  105. package/dist/components/StoreModal/StoreModalPackButton.js +53 -0
  106. package/dist/components/StoreModal/StoreModalProduct.js +38 -15
  107. package/dist/components/StoreModalProductLicenseSelection/index.d.ts +3 -3
  108. package/dist/components/StoreModalProductLicenseSelection/index.js +6 -6
  109. package/dist/components/StoreModalProductSummary/index.js +1 -1
  110. package/dist/components/elements/StoreModalFamily/index.d.ts +1 -1
  111. package/dist/components/elements/StoreModalFamily/index.js +1 -1
  112. package/dist/fontdue.css +6 -0
  113. package/dist/reducer.d.ts +7 -0
  114. package/dist/reducer.js +9 -0
  115. package/dist/selection.d.ts +5 -1
  116. package/dist/selection.js +61 -16
  117. package/dist/storeModalRoutes.js +5 -4
  118. package/dist/version.js +1 -1
  119. package/package.json +1 -1
  120. package/dist/__generated__/BuyButton_collection.graphql.d.ts +0 -25
  121. package/dist/__generated__/BuyButton_collection.graphql.js +0 -49
  122. package/fontdue.css +0 -2733
  123. package/fontdue.css.map +0 -1
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
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 _NodePasswordFormElementPackSlugQuery from "../../__generated__/NodePasswordFormElementPackSlugQuery.graphql.js";
4
5
  import _NodePasswordFormElementSlugQuery from "../../__generated__/NodePasswordFormElementSlugQuery.graphql.js";
5
6
  import _NodePasswordFormElementIDQuery from "../../__generated__/NodePasswordFormElementIDQuery.graphql.js";
6
7
  import React from 'react';
@@ -8,25 +9,25 @@ import { graphql, useLazyLoadQuery } from 'react-relay';
8
9
  import NodePasswordForm from './index.js';
9
10
  // Auto-detecting wrapper behind the <fontdue-node-password-form> custom element.
10
11
  // Unlike the presentational React component (the default export of ./index, used
11
- // when the server has already detected the lock), this queries the collection
12
- // itself and renders the form only when access is denied — so it works dropped
13
- // onto any page, hiding itself for a visitor who already has access.
12
+ // when the server has already detected the lock), this queries the product itself
13
+ // – a collection or a font pack – and renders the form only when access is
14
+ // denied, so it works dropped onto any page, hiding itself for a visitor who
15
+ // already has access.
14
16
 
15
- const idQuery = (_NodePasswordFormElementIDQuery.hash && _NodePasswordFormElementIDQuery.hash !== "213a61ae079e4f57b7b197b0d2f5256a" && console.error("The definition of 'NodePasswordFormElementIDQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NodePasswordFormElementIDQuery);
17
+ // `node(id:)` resolves a collection and a pack alike, so one query serves both.
18
+ const idQuery = (_NodePasswordFormElementIDQuery.hash && _NodePasswordFormElementIDQuery.hash !== "fa35f237c6ea919a7afbe22c86ca19b5" && console.error("The definition of 'NodePasswordFormElementIDQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NodePasswordFormElementIDQuery);
16
19
  function NodePasswordFormIDQueryRenderer(_ref) {
17
20
  let {
18
- collectionId,
19
- ...rest
21
+ nodeId,
22
+ ...props
20
23
  } = _ref;
21
24
  const data = useLazyLoadQuery(idQuery, {
22
- collectionId
25
+ nodeId
23
26
  });
24
27
  // The @catch directive surfaces the password-protection error as ok:false; the
25
28
  // node is only locked then, so only then do we render the form (we don't want
26
29
  // to show it to a visitor who already has access).
27
- if (data.collection.ok === false) return /*#__PURE__*/React.createElement(NodePasswordForm, _extends({}, rest, {
28
- collectionId: collectionId
29
- }));
30
+ if (data.node.ok === false) return /*#__PURE__*/React.createElement(NodePasswordForm, props);
30
31
  return null;
31
32
  }
32
33
  const slugQuery = (_NodePasswordFormElementSlugQuery.hash && _NodePasswordFormElementSlugQuery.hash !== "200a33ca6a1f9e4e7e95b7100bbdd261" && console.error("The definition of 'NodePasswordFormElementSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NodePasswordFormElementSlugQuery);
@@ -34,32 +35,55 @@ function NodePasswordFormSlugQueryRenderer(_ref2) {
34
35
  var _data$viewer$slug;
35
36
  let {
36
37
  collectionSlug,
37
- ...rest
38
+ ...props
38
39
  } = _ref2;
39
40
  const data = useLazyLoadQuery(slugQuery, {
40
41
  collectionSlug
41
42
  });
42
43
  // see note in NodePasswordFormIDQueryRenderer
43
- if (((_data$viewer$slug = data.viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.collection.ok) === false) return /*#__PURE__*/React.createElement(NodePasswordForm, _extends({}, rest, {
44
+ if (((_data$viewer$slug = data.viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.collection.ok) === false) return /*#__PURE__*/React.createElement(NodePasswordForm, _extends({}, props, {
44
45
  collectionSlug: collectionSlug
45
46
  }));
46
47
  return null;
47
48
  }
48
- export default function NodePasswordFormElement(_ref3) {
49
+ const packSlugQuery = (_NodePasswordFormElementPackSlugQuery.hash && _NodePasswordFormElementPackSlugQuery.hash !== "3186720cb9ad751d233e995a102efcd1" && console.error("The definition of 'NodePasswordFormElementPackSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NodePasswordFormElementPackSlugQuery);
50
+ function NodePasswordFormPackSlugQueryRenderer(_ref3) {
51
+ var _data$viewer$slug2;
49
52
  let {
53
+ fontPackSlug,
54
+ ...props
55
+ } = _ref3;
56
+ const data = useLazyLoadQuery(packSlugQuery, {
57
+ fontPackSlug
58
+ });
59
+ // see note in NodePasswordFormIDQueryRenderer
60
+ if (((_data$viewer$slug2 = data.viewer.slug) === null || _data$viewer$slug2 === void 0 ? void 0 : _data$viewer$slug2.pack.ok) === false) return /*#__PURE__*/React.createElement(NodePasswordForm, _extends({}, props, {
61
+ fontPackSlug: fontPackSlug
62
+ }));
63
+ return null;
64
+ }
65
+ export default function NodePasswordFormElement(props) {
66
+ const {
50
67
  collectionId,
51
68
  collectionSlug,
52
- ...rest
53
- } = _ref3;
54
- if (collectionId) {
69
+ fontPackId,
70
+ fontPackSlug
71
+ } = props;
72
+ const nodeId = collectionId || fontPackId;
73
+ if (nodeId) {
55
74
  return /*#__PURE__*/React.createElement(NodePasswordFormIDQueryRenderer, _extends({
56
- collectionId: collectionId
57
- }, rest));
75
+ nodeId: nodeId
76
+ }, props));
58
77
  }
59
78
  if (collectionSlug) {
60
- return /*#__PURE__*/React.createElement(NodePasswordFormSlugQueryRenderer, _extends({
79
+ return /*#__PURE__*/React.createElement(NodePasswordFormSlugQueryRenderer, _extends({}, props, {
61
80
  collectionSlug: collectionSlug
62
- }, rest));
81
+ }));
82
+ }
83
+ if (fontPackSlug) {
84
+ return /*#__PURE__*/React.createElement(NodePasswordFormPackSlugQueryRenderer, _extends({}, props, {
85
+ fontPackSlug: fontPackSlug
86
+ }));
63
87
  }
64
- throw new Error('NodePasswordForm expected either a collectionId or collectionSlug prop');
88
+ throw new Error('NodePasswordForm expected a collectionId, collectionSlug, fontPackId or fontPackSlug prop');
65
89
  }
@@ -2,6 +2,9 @@ import React from 'react';
2
2
  export interface NodePasswordForm_props {
3
3
  collectionId?: string | null;
4
4
  collectionSlug?: string | null;
5
+ /** A font pack's identifiers, in place of the collection ones. */
6
+ fontPackId?: string | null;
7
+ fontPackSlug?: string | null;
5
8
  /**
6
9
  * Path of the storefront's unlock route (see fontdue-js/next/unlock), e.g.
7
10
  * "/api/unlock". On a successful unlock the form POSTs `{ token }` there
@@ -15,10 +18,10 @@ export interface NodePasswordForm_props {
15
18
  unlockEndpoint?: string | null;
16
19
  }
17
20
  /**
18
- * Password form for a collection you already know is locked. Render it when a
19
- * collection comes back password-protected (see the per-framework guides): it
20
- * commits the `accessNode` mutation, remembers the returned access token, and
21
- * reloads so the now-unlocked collection renders.
21
+ * Password form for a collection or font pack you already know is locked.
22
+ * Render it when the product comes back password-protected (see the
23
+ * per-framework guides): it commits the `accessNode` mutation, remembers the
24
+ * returned access token, and reloads so the now-unlocked product renders.
22
25
  *
23
26
  * Unlike the `<fontdue-node-password-form>` custom element (which detects the
24
27
  * locked state itself), this does no data fetching of its own, so it renders
@@ -8,10 +8,10 @@ import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
8
8
  import { rememberNodeAccessToken } from '../../nodeAccess.js';
9
9
  const accessNodeMutation = (_NodePasswordFormAccessNodeMutation.hash && _NodePasswordFormAccessNodeMutation.hash !== "eb0d3828d65a52c727ef60caa67709a1" && console.error("The definition of 'NodePasswordFormAccessNodeMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NodePasswordFormAccessNodeMutation);
10
10
  /**
11
- * Password form for a collection you already know is locked. Render it when a
12
- * collection comes back password-protected (see the per-framework guides): it
13
- * commits the `accessNode` mutation, remembers the returned access token, and
14
- * reloads so the now-unlocked collection renders.
11
+ * Password form for a collection or font pack you already know is locked.
12
+ * Render it when the product comes back password-protected (see the
13
+ * per-framework guides): it commits the `accessNode` mutation, remembers the
14
+ * returned access token, and reloads so the now-unlocked product renders.
15
15
  *
16
16
  * Unlike the `<fontdue-node-password-form>` custom element (which detects the
17
17
  * locked state itself), this does no data fetching of its own, so it renders
@@ -65,8 +65,8 @@ function NodePasswordFormFields(props) {
65
65
  variables: {
66
66
  input: {
67
67
  password,
68
- id: props.collectionId,
69
- slug: props.collectionSlug
68
+ id: props.collectionId || props.fontPackId || undefined,
69
+ slug: props.collectionSlug || props.fontPackSlug || undefined
70
70
  }
71
71
  },
72
72
  onCompleted: res => {
@@ -1,4 +1,5 @@
1
1
  import _productState_Query from "../../__generated__/productState_Query.graphql.js";
2
+ import _productState_fontPack from "../../__generated__/productState_fontPack.graphql.js";
2
3
  import _productState_node from "../../__generated__/productState_node.graphql.js";
3
4
  import _productState_collectionBundle from "../../__generated__/productState_collectionBundle.graphql.js";
4
5
  import _productState_bundle from "../../__generated__/productState_bundle.graphql.js";
@@ -11,7 +12,13 @@ _productState_collection.hash && _productState_collection.hash !== "90e56933f956
11
12
  _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;
12
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;
13
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;
14
- const query = (_productState_Query.hash && _productState_Query.hash !== "ac3292bae9a274139d5a525fd0162711" && console.error("The definition of 'productState_Query' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_Query);
15
+
16
+ // A pack is one SKU that grants every style in `fontStyles`. Its collection
17
+ // members (a family or a superfamily with its families) are fetched in the
18
+ // same shape as a collection product so they register as ordinary
19
+ // collections, with the pack sitting above them.
20
+ _productState_fontPack.hash && _productState_fontPack.hash !== "43a236a4ddb4436a51d765a07e2b0eac" && console.error("The definition of 'productState_fontPack' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_fontPack;
21
+ const query = (_productState_Query.hash && _productState_Query.hash !== "65de750413d1564c4f3e1ec44ec7e2a2" && console.error("The definition of 'productState_Query' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _productState_Query);
15
22
  export const populateProductState = (store, environment, id) => {
16
23
  const existingState = store.getState().fetchedCollectionIds;
17
24
  if (existingState && existingState.indexOf(id) >= 0) return;
@@ -24,6 +31,7 @@ export const populateProductState = (store, environment, id) => {
24
31
  })
25
32
  });
26
33
  };
34
+ const packMemberCollections = pack => pack.members.filter(member => member.__typename === 'FontCollection');
27
35
  const allSkus = function (collection) {
28
36
  var _collection$fontStyle;
29
37
  let includeCollections = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
@@ -98,19 +106,54 @@ const flattenSkuStyleIds = collection => {
98
106
  for (const child of nested) Object.assign(result, flattenSkuStyleIds(child));
99
107
  return result;
100
108
  };
109
+
110
+ // The pack's SKU grants every style the pack lists, its style members' SKUs
111
+ // their one style each, and its collection members (a family, or a superfamily
112
+ // with its families) register like the collections they are – so the pack sits
113
+ // over them the way a superfamily sits over its families.
114
+ const flattenPackSkuStyleIds = pack => {
115
+ const result = {};
116
+ for (const member of packMemberCollections(pack)) {
117
+ Object.assign(result, flattenSkuStyleIds(member));
118
+ }
119
+ for (const style of pack.fontStyles) {
120
+ if (style.sku) result[style.sku.id] = [style.id];
121
+ }
122
+ const styleIds = pack.fontStyles.map(style => style.id);
123
+ if (pack.sku && styleIds.length > 0) result[pack.sku.id] = styleIds;
124
+ return result;
125
+ };
101
126
  const flattenSkuPrices = collection => {
102
127
  return allSkus(collection).reduce((acc, sku) => {
103
128
  acc[sku.id] = sku.price.amount;
104
129
  return acc;
105
130
  }, {});
106
131
  };
132
+ const flattenPackSkuPrices = pack => {
133
+ const skus = [pack.sku, ...pack.fontStyles.map(style => style.sku), ...packMemberCollections(pack).flatMap(member => allSkus(member))].filter(notEmpty);
134
+ return skus.reduce((acc, sku) => {
135
+ acc[sku.id] = sku.price.amount;
136
+ return acc;
137
+ }, {});
138
+ };
107
139
  export const populateProductStateReducer = (state, action) => {
108
- var _data$node, _data$node$sku;
140
+ var _node$sku;
109
141
  if (action.type !== 'POPULATE_PRODUCT_STATE') return state;
110
142
  const {
111
143
  data
112
144
  } = action;
113
- if (((_data$node = data.node) === null || _data$node === void 0 ? void 0 : _data$node.__typename) !== 'FontCollection') return state;
145
+ const node = data.node;
146
+ let skuStyleIds;
147
+ let skuPrices;
148
+ if ((node === null || node === void 0 ? void 0 : node.__typename) === 'FontCollection') {
149
+ skuStyleIds = flattenSkuStyleIds(node);
150
+ skuPrices = flattenSkuPrices(node);
151
+ } else if ((node === null || node === void 0 ? void 0 : node.__typename) === 'FontPack') {
152
+ skuStyleIds = flattenPackSkuStyleIds(node);
153
+ skuPrices = flattenPackSkuPrices(node);
154
+ } else {
155
+ return state;
156
+ }
114
157
  return {
115
158
  ...state,
116
159
  // {
@@ -118,22 +161,29 @@ export const populateProductStateReducer = (state, action) => {
118
161
  // }
119
162
  skuStyleIds: {
120
163
  ...state.skuStyleIds,
121
- ...flattenSkuStyleIds(data.node)
164
+ ...skuStyleIds
122
165
  },
123
166
  // {
124
167
  // [skuId]: amount,
125
168
  // }
126
169
  skuPrices: {
127
170
  ...state.skuPrices,
128
- ...flattenSkuPrices(data.node)
171
+ ...skuPrices
129
172
  },
130
173
  // {
131
174
  // [collectionId]: [skuId]
132
175
  // }
133
176
  collectionSkus: {
134
177
  ...state.collectionSkus,
135
- [`${data.node.id}`]: ((_data$node$sku = data.node.sku) === null || _data$node$sku === void 0 ? void 0 : _data$node$sku.id) ?? null
178
+ [`${node.id}`]: ((_node$sku = node.sku) === null || _node$sku === void 0 ? void 0 : _node$sku.id) ?? null
179
+ },
180
+ // {
181
+ // [productId]: [skuId...]
182
+ // }
183
+ productSkuIds: {
184
+ ...state.productSkuIds,
185
+ [`${node.id}`]: Object.keys(skuPrices)
136
186
  },
137
- fetchedCollectionIds: state.fetchedCollectionIds.concat(data.node.id)
187
+ fetchedCollectionIds: state.fetchedCollectionIds.concat(node.id)
138
188
  };
139
189
  };
@@ -5,5 +5,12 @@ interface StoreModalFamilyProps {
5
5
  onSelect: (skuId: string, selected: boolean) => void;
6
6
  title?: React.ReactNode;
7
7
  }
8
+ interface GroupableStyle {
9
+ cssFamily: string | null;
10
+ cssWeight: string | null;
11
+ cssStretch: string | null;
12
+ cssStyle: string | null;
13
+ }
14
+ export declare function stylesGrouped<T extends GroupableStyle>(fontStyles: ReadonlyArray<T> | null | undefined): ReadonlyArray<ReadonlyArray<T>>;
8
15
  export default function StoreModalFamily({ collection: collectionKey, onSelect, title, }: StoreModalFamilyProps): React.JSX.Element | null;
9
16
  export {};
@@ -17,9 +17,11 @@ function chunkConsecutiveBy(collection, compare) {
17
17
  }, []);
18
18
  }
19
19
  const isSlanted = cssStyle => cssStyle === 'italic' || cssStyle === 'oblique';
20
- const stylesGrouped = collection => {
21
- if (!collection.fontStyles) return [];
22
- const chunks = chunkConsecutiveBy(collection.fontStyles, (left, right) => left.cssFamily === right.cssFamily && left.cssWeight === right.cssWeight && left.cssStretch === right.cssStretch && isSlanted(left.cssStyle) !== isSlanted(right.cssStyle));
20
+ // Pairs each upright style with its italic/oblique so the two render side by
21
+ // side. Shared with the font pack view, which groups a pack's style members.
22
+ export function stylesGrouped(fontStyles) {
23
+ if (!fontStyles) return [];
24
+ const chunks = chunkConsecutiveBy(fontStyles, (left, right) => left.cssFamily === right.cssFamily && left.cssWeight === right.cssWeight && left.cssStretch === right.cssStretch && isSlanted(left.cssStyle) !== isSlanted(right.cssStyle));
23
25
 
24
26
  // Only keep a chunk paired when it's exactly one upright + one slanted
25
27
  // (italic or oblique) style. Anything else (3+ consecutive matches, or
@@ -30,7 +32,7 @@ const stylesGrouped = collection => {
30
32
  const slantedCount = chunk.filter(s => isSlanted(s.cssStyle)).length;
31
33
  return slantedCount === 1 ? [chunk] : chunk.map(style => [style]);
32
34
  });
33
- };
35
+ }
34
36
  export default function StoreModalFamily(_ref) {
35
37
  var _collection$bundles;
36
38
  let {
@@ -39,7 +41,7 @@ export default function StoreModalFamily(_ref) {
39
41
  title
40
42
  } = _ref;
41
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);
42
- const fontStyles = useMemo(() => stylesGrouped(collection), [collection]);
44
+ const fontStyles = useMemo(() => stylesGrouped(collection.fontStyles), [collection]);
43
45
  if ((fontStyles === null || fontStyles === void 0 ? void 0 : fontStyles.length) === 0 && collection.collectionType === 'superfamily' && !collection.sku) return null;
44
46
  return /*#__PURE__*/React.createElement(FamilyElement, null, {
45
47
  title,
@@ -6,6 +6,7 @@ import StoreModalPageContainer from '../elements/StoreModalPageContainer/index.j
6
6
  import StoreModalIndexLoadMore from '../elements/StoreModalIndexLoadMore/index.js';
7
7
  import StoreModalContainerContext from '../elements/StoreModalContainer/StoreModalContainerContext.js';
8
8
  import StoreModalIndexItem from './StoreModalIndexItem.js';
9
+ import StoreModalIndexPackItem from './StoreModalIndexPackItem.js';
9
10
  import useInfiniteScroll from '../useInfiniteScroll.js';
10
11
 
11
12
  // How many collections arrive at a time. Enough that a typical catalogue is one
@@ -18,12 +19,17 @@ const PAGE_SIZE = 25;
18
19
  // Start the next page while the sentinel is still this far below the modal's
19
20
  // visible area, so the pages join up without the visitor reaching a dead end.
20
21
  const LOAD_MORE_MARGIN = '0px 0px 600px 0px';
22
+ // Font packs come first: they're the curated, cross-catalog offers, and there
23
+ // are few of them next to the families – so they arrive whole, with the first
24
+ // page of collections, rather than paginated. Both lists honour the same
25
+ // `indexExcludeTags`, and the backend already leaves out hidden, unlisted and
26
+ // still-locked password-protected packs, as it does for collections.
21
27
  const StoreModalIndex = _ref => {
22
- var _data$rootCollections;
28
+ var _queryData$viewer, _queryData$viewer$fon, _data$rootCollections;
23
29
  let {
24
30
  prepared
25
31
  } = _ref;
26
- const queryData = usePreloadedQuery((_StoreModalIndexQuery.hash && _StoreModalIndexQuery.hash !== "125a10155b690929f0eae6e9e6c7188c" && console.error("The definition of 'StoreModalIndexQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalIndexQuery), prepared.indexQuery);
32
+ const queryData = usePreloadedQuery((_StoreModalIndexQuery.hash && _StoreModalIndexQuery.hash !== "cea53722e4ce3c40a68997218f7e979f" && console.error("The definition of 'StoreModalIndexQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalIndexQuery), prepared.indexQuery);
27
33
 
28
34
  // The query type only carries the fragment spread, not its data type, so
29
35
  // both sides are named explicitly for `data` to be typed.
@@ -47,9 +53,13 @@ const StoreModalIndex = _ref => {
47
53
  rootRef: scrollContainer,
48
54
  rootMargin: LOAD_MORE_MARGIN
49
55
  });
56
+ const packEdges = ((_queryData$viewer = queryData.viewer) === null || _queryData$viewer === void 0 ? void 0 : (_queryData$viewer$fon = _queryData$viewer.fontPacks) === null || _queryData$viewer$fon === void 0 ? void 0 : _queryData$viewer$fon.edges) ?? [];
50
57
  const edges = (data === null || data === void 0 ? void 0 : (_data$rootCollections = data.rootCollections) === null || _data$rootCollections === void 0 ? void 0 : _data$rootCollections.edges) ?? [];
51
58
  return /*#__PURE__*/React.createElement(StoreModalPageContainer, null, {
52
- body: /*#__PURE__*/React.createElement(React.Fragment, null, edges.map(edge => /*#__PURE__*/React.createElement(StoreModalIndexItem, {
59
+ body: /*#__PURE__*/React.createElement(React.Fragment, null, packEdges.map(edge => /*#__PURE__*/React.createElement(StoreModalIndexPackItem, {
60
+ pack: edge.node,
61
+ key: edge.node.id
62
+ })), edges.map(edge => /*#__PURE__*/React.createElement(StoreModalIndexItem, {
53
63
  collection: edge.node,
54
64
  key: edge.node.id
55
65
  })), hasNext && /*#__PURE__*/React.createElement(StoreModalIndexLoadMore, {
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { FontStyle_fontStyle$key } from '__generated__/FontStyle_fontStyle.graphql.js';
3
+ type FeatureStyle = FontStyle_fontStyle$key & {
4
+ readonly supportedLanguages: readonly string[] | null | undefined;
5
+ };
6
+ interface StoreModalIndexEntryProps {
7
+ productId: string;
8
+ name: string;
9
+ featureStyle: FeatureStyle | null | undefined;
10
+ label: string;
11
+ }
12
+ declare const StoreModalIndexEntry: ({ productId, name, featureStyle, label, }: StoreModalIndexEntryProps) => React.JSX.Element;
13
+ export default StoreModalIndexEntry;
@@ -0,0 +1,43 @@
1
+ import React, { useContext } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import StoreModalIndexItemElement from '../elements/StoreModalIndexItem/index.js';
4
+ import ConfigContext from '../ConfigContext.js';
5
+ import FontStyle from '../FontStyle/index.js';
6
+ import { getFeatureGlyphs } from '../../utils.js';
7
+ // One row of the store modal's index page. Collections and packs share this
8
+ // – the `indexLayout` config decides whether the feature style draws an "Aa"
9
+ // beside the name or the name itself – and only differ in what they say
10
+ // under the name.
11
+ const StoreModalIndexEntry = _ref => {
12
+ let {
13
+ productId,
14
+ name,
15
+ featureStyle,
16
+ label
17
+ } = _ref;
18
+ const {
19
+ storeModal: config
20
+ } = useContext(ConfigContext);
21
+ const dispatch = useDispatch();
22
+ const handleClick = () => {
23
+ dispatch({
24
+ type: 'STORE_MODAL_NAVIGATE',
25
+ route: 'product',
26
+ variables: {
27
+ productId
28
+ }
29
+ });
30
+ };
31
+ return /*#__PURE__*/React.createElement(StoreModalIndexItemElement, {
32
+ onClick: handleClick
33
+ }, {
34
+ aa: config.indexLayout === 'styled-aa' && (featureStyle ? /*#__PURE__*/React.createElement(FontStyle, {
35
+ fontStyle: featureStyle
36
+ }, getFeatureGlyphs(featureStyle.supportedLanguages ?? null)) : null),
37
+ name: config.indexLayout === 'styled-font-names' ? featureStyle && /*#__PURE__*/React.createElement(FontStyle, {
38
+ fontStyle: featureStyle
39
+ }, name) : name,
40
+ label
41
+ });
42
+ };
43
+ export default StoreModalIndexEntry;
@@ -1,31 +1,14 @@
1
1
  import _StoreModalIndexItem_fontCollection from "../../__generated__/StoreModalIndexItem_fontCollection.graphql.js";
2
- import React, { useContext } from 'react';
3
- import { useDispatch } from 'react-redux';
2
+ import React from 'react';
4
3
  import { graphql, useFragment } from 'react-relay';
5
- import StoreModalIndexItemElement from '../elements/StoreModalIndexItem/index.js';
6
- import ConfigContext from '../ConfigContext.js';
7
- import FontStyle from '../FontStyle/index.js';
4
+ import StoreModalIndexEntry from './StoreModalIndexEntry.js';
8
5
  import useTotalStyles from '../useTotalStyles.js';
9
- import { getFeatureGlyphs, pluralize } from '../../utils.js';
6
+ import { pluralize } from '../../utils.js';
10
7
  const StoreModalIndexItem = _ref => {
11
- var _data$featureStyle;
12
8
  let {
13
9
  collection
14
10
  } = _ref;
15
- const {
16
- storeModal: config
17
- } = useContext(ConfigContext);
18
11
  const data = useFragment((_StoreModalIndexItem_fontCollection.hash && _StoreModalIndexItem_fontCollection.hash !== "8a990fad8dc50c9f00b391902e9bc482" && console.error("The definition of 'StoreModalIndexItem_fontCollection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalIndexItem_fontCollection), collection);
19
- const dispatch = useDispatch();
20
- const handleClickProduct = productId => {
21
- dispatch({
22
- type: 'STORE_MODAL_NAVIGATE',
23
- route: 'product',
24
- variables: {
25
- productId
26
- }
27
- });
28
- };
29
12
  const {
30
13
  totalStyles,
31
14
  variableFonts
@@ -39,16 +22,10 @@ const StoreModalIndexItem = _ref => {
39
22
  if (variableFonts > 0) {
40
23
  label.push(pluralize(variableFonts, 'variable font'));
41
24
  }
42
- return /*#__PURE__*/React.createElement(StoreModalIndexItemElement, {
43
- key: data.id,
44
- onClick: handleClickProduct.bind(null, data.id)
45
- }, {
46
- aa: config.indexLayout === 'styled-aa' && (data.featureStyle ? /*#__PURE__*/React.createElement(FontStyle, {
47
- fontStyle: data.featureStyle
48
- }, getFeatureGlyphs((_data$featureStyle = data.featureStyle) === null || _data$featureStyle === void 0 ? void 0 : _data$featureStyle.supportedLanguages)) : null),
49
- name: config.indexLayout === 'styled-font-names' ? data.featureStyle && /*#__PURE__*/React.createElement(FontStyle, {
50
- fontStyle: data.featureStyle
51
- }, data.name) : data.name,
25
+ return /*#__PURE__*/React.createElement(StoreModalIndexEntry, {
26
+ productId: data.id,
27
+ name: data.name,
28
+ featureStyle: data.featureStyle,
52
29
  label: label.join(', ')
53
30
  });
54
31
  };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { StoreModalIndexPackItem_fontPack$key } from '__generated__/StoreModalIndexPackItem_fontPack.graphql.js';
3
+ interface StoreModalIndexPackItemProps {
4
+ pack: StoreModalIndexPackItem_fontPack$key;
5
+ }
6
+ declare const StoreModalIndexPackItem: ({ pack }: StoreModalIndexPackItemProps) => React.JSX.Element;
7
+ export default StoreModalIndexPackItem;
@@ -0,0 +1,21 @@
1
+ import _StoreModalIndexPackItem_fontPack from "../../__generated__/StoreModalIndexPackItem_fontPack.graphql.js";
2
+ import React from 'react';
3
+ import { graphql, useFragment } from 'react-relay';
4
+ import StoreModalIndexEntry from './StoreModalIndexEntry.js';
5
+ import { pluralize } from '../../utils.js';
6
+ // A pack's name alone ("Anniversary Pack") doesn't tell a visitor it spans
7
+ // families, so its subhead says what it is before the style count.
8
+ const packIndexLabel = totalStyles => totalStyles ? `Font pack • ${pluralize(totalStyles, 'style')}` : 'Font pack';
9
+ const StoreModalIndexPackItem = _ref => {
10
+ let {
11
+ pack
12
+ } = _ref;
13
+ const data = useFragment((_StoreModalIndexPackItem_fontPack.hash && _StoreModalIndexPackItem_fontPack.hash !== "47273d9e96f713344a4e62f4c5517161" && console.error("The definition of 'StoreModalIndexPackItem_fontPack' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalIndexPackItem_fontPack), pack);
14
+ return /*#__PURE__*/React.createElement(StoreModalIndexEntry, {
15
+ productId: data.id,
16
+ name: data.name,
17
+ featureStyle: data.featureStyle,
18
+ label: packIndexLabel(data.totalStyles)
19
+ });
20
+ };
21
+ export default StoreModalIndexPackItem;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { StoreModalPack_fontPack$key } from '../../__generated__/StoreModalPack_fontPack.graphql.js';
3
+ interface StoreModalPackProps {
4
+ pack: StoreModalPack_fontPack$key;
5
+ onSelect: (skuId: string, selected: boolean) => void;
6
+ title?: React.ReactNode;
7
+ }
8
+ export default function StoreModalPack({ pack: packKey, onSelect, title }: StoreModalPackProps): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,79 @@
1
+ import _StoreModalPack_fontPack from "../../__generated__/StoreModalPack_fontPack.graphql.js";
2
+ import React, { Fragment, useMemo } from 'react';
3
+ import { graphql, useFragment } from 'react-relay';
4
+ import StoreModalFamily, { stylesGrouped } from './StoreModalFamily.js';
5
+ import StoreModalPackButton from './StoreModalPackButton.js';
6
+ import StyleButton from './StoreModalStyleButton.js';
7
+ import FamilyElement from '../elements/StoreModalFamily/index.js';
8
+
9
+ // A pack's members render as family blocks: a collection member is the whole
10
+ // family (or superfamily) as it appears on its own product page; style
11
+ // members are gathered under their family, in the order the first of them
12
+ // appears, as a partial block of just those styles.
13
+
14
+ // An unlisted collection has no block anywhere in the Store Modal – no family
15
+ // button, bundles or styles (see StoreModalProduct). A pack still grants it,
16
+ // and the pack button still sells it, but it isn't offered on its own here.
17
+ const memberBlocks = members => members.reduce((blocks, member) => {
18
+ var _member$family, _member$family2, _member$family3, _member$family4;
19
+ if (member.__typename === 'FontCollection') {
20
+ if (member.visibility === 'UNLISTED') return blocks;
21
+ return blocks.concat({
22
+ kind: 'collection',
23
+ collection: member
24
+ });
25
+ }
26
+ if (member.__typename !== 'FontStyle') return blocks;
27
+ if (((_member$family = member.family) === null || _member$family === void 0 ? void 0 : _member$family.visibility) === 'UNLISTED') return blocks;
28
+ const familyId = ((_member$family2 = member.family) === null || _member$family2 === void 0 ? void 0 : _member$family2.id) ?? member.id;
29
+ const existing = blocks.find(block => block.kind === 'styles' && block.familyId === familyId);
30
+ if ((existing === null || existing === void 0 ? void 0 : existing.kind) === 'styles') {
31
+ existing.styles.push(member);
32
+ return blocks;
33
+ }
34
+ return blocks.concat({
35
+ kind: 'styles',
36
+ familyId,
37
+ familyName: ((_member$family3 = member.family) === null || _member$family3 === void 0 ? void 0 : _member$family3.name) ?? '',
38
+ isVariableFont: ((_member$family4 = member.family) === null || _member$family4 === void 0 ? void 0 : _member$family4.isVariableFont) ?? false,
39
+ styles: [member]
40
+ });
41
+ }, []);
42
+ export default function StoreModalPack(_ref) {
43
+ let {
44
+ pack: packKey,
45
+ onSelect,
46
+ title
47
+ } = _ref;
48
+ const pack = useFragment((_StoreModalPack_fontPack.hash && _StoreModalPack_fontPack.hash !== "5bbb13828a56279be2b22df22ad4aae0" && console.error("The definition of 'StoreModalPack_fontPack' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalPack_fontPack), packKey);
49
+ const blocks = useMemo(() => memberBlocks(pack.members), [pack.members]);
50
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FamilyElement, null, {
51
+ title,
52
+ familyButton: /*#__PURE__*/React.createElement(StoreModalPackButton, {
53
+ pack: pack,
54
+ onSelectSku: onSelect
55
+ })
56
+ }), blocks.map(block => {
57
+ var _block$collection$chi;
58
+ return block.kind === 'collection' ? /*#__PURE__*/React.createElement(Fragment, {
59
+ key: `collection:${block.collection.id}`
60
+ }, /*#__PURE__*/React.createElement(StoreModalFamily, {
61
+ collection: block.collection,
62
+ onSelect: onSelect
63
+ }), (_block$collection$chi = block.collection.children) === null || _block$collection$chi === void 0 ? void 0 : _block$collection$chi.filter(child => child.visibility !== 'UNLISTED').map(child => /*#__PURE__*/React.createElement(StoreModalFamily, {
64
+ key: child.id,
65
+ collection: child,
66
+ onSelect: onSelect
67
+ }))) : /*#__PURE__*/React.createElement(FamilyElement, {
68
+ key: `styles:${block.familyId}`
69
+ }, {
70
+ title: block.familyName,
71
+ styleGroups: stylesGrouped(block.styles).map(group => group.map(fontStyle => /*#__PURE__*/React.createElement(StyleButton, {
72
+ key: fontStyle.id,
73
+ fontStyle: fontStyle,
74
+ onSelectSku: onSelect,
75
+ isVariableFont: block.isVariableFont
76
+ })))
77
+ });
78
+ }));
79
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { StoreModalPackButton_fontPack$key } from '../../__generated__/StoreModalPackButton_fontPack.graphql.js';
3
+ interface PackButtonProps {
4
+ pack: StoreModalPackButton_fontPack$key;
5
+ onSelectSku: (skuId: string, selected: boolean) => void;
6
+ }
7
+ export default function StoreModalPackButton({ pack: packKey, onSelectSku, ...rest }: PackButtonProps): React.JSX.Element;
8
+ export {};