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
@@ -0,0 +1,53 @@
1
+ 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); }
2
+ import _StoreModalPackButton_fontPack from "../../__generated__/StoreModalPackButton_fontPack.graphql.js";
3
+ import React from 'react';
4
+ import { graphql, useFragment } from 'react-relay';
5
+ import { useSelector } from 'react-redux';
6
+ import SKUPrice from '../SKUPrice/index.js';
7
+ import RelayPrice, { PriceFormat } from '../Price/index.js';
8
+ import { isSelected } from '../../reducer.js';
9
+ import StoreModalFamilyButtonElement from '../elements/StoreModalFamilyButton/index.js';
10
+ import FontStyle from '../FontStyle/index.js';
11
+ import { pluralize } from '../../utils.js';
12
+ // The pack's own row at the top of the product page — the family button's
13
+ // markup, buying the whole pack at its one fixed price, under the pack's name
14
+ // exactly as the admin entered it (the index, cart and BuyButton show the
15
+ // same). Beside the price it strikes through what the styles would cost one
16
+ // by one.
17
+ export default function StoreModalPackButton(_ref) {
18
+ var _pack$featureStyle, _pack$featureStyle$su, _pack$sku;
19
+ let {
20
+ pack: packKey,
21
+ onSelectSku,
22
+ ...rest
23
+ } = _ref;
24
+ const pack = useFragment((_StoreModalPackButton_fontPack.hash && _StoreModalPackButton_fontPack.hash !== "9075ef7d1f8c2ce1fa8beba0884713cb" && console.error("The definition of 'StoreModalPackButton_fontPack' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalPackButton_fontPack), packKey);
25
+ const selector = pack.sku ? isSelected(pack.sku.id) : () => false;
26
+ const selected = useSelector(selector);
27
+ const handleClick = () => {
28
+ if (!pack.sku) return;
29
+ onSelectSku(pack.sku.id, !selected);
30
+ };
31
+ const supportsEnglish = (_pack$featureStyle = pack.featureStyle) === null || _pack$featureStyle === void 0 ? void 0 : (_pack$featureStyle$su = _pack$featureStyle.supportedLanguages) === null || _pack$featureStyle$su === void 0 ? void 0 : _pack$featureStyle$su.includes('en');
32
+ const showsSaving = ((_pack$sku = pack.sku) === null || _pack$sku === void 0 ? void 0 : _pack$sku.price) && pack.totalStylesPrice && pack.totalStylesPrice.amount > pack.sku.price.amount;
33
+ return /*#__PURE__*/React.createElement(StoreModalFamilyButtonElement, _extends({
34
+ selected: selected,
35
+ onClick: pack.sku ? handleClick : undefined,
36
+ supportsEnglish: supportsEnglish ?? true
37
+ }, rest), {
38
+ name: pack.featureStyle && supportsEnglish ? /*#__PURE__*/React.createElement(FontStyle, {
39
+ Component: "span",
40
+ fontStyle: pack.featureStyle
41
+ }, pack.name) : /*#__PURE__*/React.createElement("span", null, pack.name),
42
+ label: pack.totalStyles ? pluralize(pack.totalStyles, 'style') : '',
43
+ price: pack.sku && /*#__PURE__*/React.createElement(React.Fragment, null, showsSaving && /*#__PURE__*/React.createElement("s", {
44
+ className: "store-modal__family-button__styles-price"
45
+ }, /*#__PURE__*/React.createElement(RelayPrice, {
46
+ price: pack.totalStylesPrice,
47
+ format: PriceFormat.Truncated
48
+ })), /*#__PURE__*/React.createElement(SKUPrice, {
49
+ sku: pack.sku,
50
+ format: PriceFormat.Truncated
51
+ }))
52
+ });
53
+ }
@@ -6,6 +6,7 @@ import React, { Fragment, useCallback, useContext, useEffect, useMemo } from 're
6
6
  import { graphql, useRelayEnvironment, useFragment, usePreloadedQuery, useRefetchableFragment } from 'react-relay';
7
7
  import { shallowEqual, useDispatch, useSelector, useStore } from 'react-redux';
8
8
  import StoreModalFamily from './StoreModalFamily.js';
9
+ import StoreModalPack from './StoreModalPack.js';
9
10
  import StoreModalProductSummary from '../StoreModalProductSummary/index.js';
10
11
  import StoreModalPageContainer from '../elements/StoreModalPageContainer/index.js';
11
12
  import StoreModalProductLicenseSelection from '../StoreModalProductLicenseSelection/index.js';
@@ -18,16 +19,16 @@ import StoreModalLicenseeIsBillingSelection from './StoreModalLicenseeIsBillingS
18
19
  import { useLicenseAndOrderVariables } from '../../hooks/useLicenseAndOrderVariables.js';
19
20
  import { trackProductViewed } from '../Cart/storefrontEvents.js';
20
21
 
21
- // A product is addressed by collection id or by slug — the same two
22
- // identifiers `BuyButton` takes. Two queries rather than one with `@skip`,
22
+ // A product – a collection or a font pack – is addressed by id or by slug,
23
+ // the same two identifiers `BuyButton` takes. Two queries rather than one with `@skip`,
23
24
  // so each carries only the selection it needs; both hand the same collection
24
25
  // and viewer to `StoreModalProductContent` below.
25
26
  export const productIdQuery = (_StoreModalProductQuery.hash && _StoreModalProductQuery.hash !== "e3ca6ec04f2df35aa68baf439c70cf42" && console.error("The definition of 'StoreModalProductQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductQuery);
26
27
 
27
- // No `@required` on `fontCollection` here, unlike BuyButton: a slug naming
28
- // nothing — or naming an article rather than a collection — should land on
29
- // StoreModalProductContent's "Collection not found", not throw.
30
- export const productSlugQuery = (_StoreModalProductSlugQuery.hash && _StoreModalProductSlugQuery.hash !== "f0bb35ed9f88ad214b40fd1f3b92fed0" && console.error("The definition of 'StoreModalProductSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductSlugQuery);
28
+ // No `@required` on `fontCollection` / `fontPack` here, unlike BuyButton: a
29
+ // slug naming nothing — or naming an article rather than a product — should
30
+ // land on StoreModalProductContent's "Product not found", not throw.
31
+ export const productSlugQuery = (_StoreModalProductSlugQuery.hash && _StoreModalProductSlugQuery.hash !== "bd2c1bf7b7b154501ea47213af58725e" && console.error("The definition of 'StoreModalProductSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductSlugQuery);
31
32
  const productViewerFragment = (_StoreModalProductViewer.hash && _StoreModalProductViewer.hash !== "3d2fc7da461822a44bb26e9b3f1715fb" && console.error("The definition of 'StoreModalProductViewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductViewer);
32
33
  const StoreModalProductContent = _ref => {
33
34
  var _config$storeModal, _viewer$orderVariable, _viewer$settings, _viewer$settings2, _viewer$settings3, _viewer$settings4, _viewer$settings5, _viewer$settings6, _viewer$settings7, _viewer$settings8;
@@ -38,8 +39,11 @@ const StoreModalProductContent = _ref => {
38
39
  const config = useContext(ConfigContext);
39
40
  const environment = useRelayEnvironment();
40
41
  const viewer = useFragment(productViewerFragment, viewerRef);
41
- const [data, refetch] = useRefetchableFragment((_StoreModalProductContent_collection.hash && _StoreModalProductContent_collection.hash !== "f7a9e591b5e1596f4d518fc5d9d46e91" && console.error("The definition of 'StoreModalProductContent_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductContent_collection), collectionRef ?? null);
42
- const collection = data;
42
+ const [data, refetch] = useRefetchableFragment((_StoreModalProductContent_collection.hash && _StoreModalProductContent_collection.hash !== "c8ce87bca2032637d922f535fc168f56" && console.error("The definition of 'StoreModalProductContent_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductContent_collection), collectionRef ?? null);
43
+
44
+ // The product page sells a collection or a font pack; anything else the id
45
+ // resolves to is not a product.
46
+ const collection = (data === null || data === void 0 ? void 0 : data.__typename) === 'FontCollection' || (data === null || data === void 0 ? void 0 : data.__typename) === 'FontPack' ? data : null;
43
47
  const refetchVariables = useMemo(() => collection !== null && collection !== void 0 && collection.id ? {
44
48
  id: collection.id
45
49
  } : undefined, [collection === null || collection === void 0 ? void 0 : collection.id]);
@@ -61,6 +65,21 @@ const StoreModalProductContent = _ref => {
61
65
  useEffect(() => {
62
66
  if (collection !== null && collection !== void 0 && collection.id) populateProductState(store, environment, collection.id);
63
67
  }, [collection === null || collection === void 0 ? void 0 : collection.id]);
68
+
69
+ // The page reports the product it shows, whether the route named it by id or
70
+ // by slug, so a selection made on it stays within what it shows
71
+ // (selection.ts).
72
+ useEffect(() => {
73
+ if (!(collection !== null && collection !== void 0 && collection.id)) return;
74
+ dispatch({
75
+ type: 'STORE_MODAL_VIEW_PRODUCT',
76
+ productId: collection.id
77
+ });
78
+ return () => dispatch({
79
+ type: 'STORE_MODAL_VIEW_PRODUCT',
80
+ productId: null
81
+ });
82
+ }, [collection === null || collection === void 0 ? void 0 : collection.id]);
64
83
  useEffect(() => {
65
84
  if (collection !== null && collection !== void 0 && collection.id) trackProductViewed(environment, collection.id);
66
85
  }, [collection === null || collection === void 0 ? void 0 : collection.id]);
@@ -87,7 +106,7 @@ const StoreModalProductContent = _ref => {
87
106
  const requiredOrderVariablesNotYetSelected = (_viewer$orderVariable = viewer.orderVariables) === null || _viewer$orderVariable === void 0 ? void 0 : _viewer$orderVariable.filter(variable => variable.variableType === 'select').find(variable => !orderVariableSelections.find(selection => selection.orderVariableId === variable.id));
88
107
  if (!collection) {
89
108
  return /*#__PURE__*/React.createElement(StoreModalPageContainer, null, {
90
- body: 'Collection not found'
109
+ body: 'Product not found'
91
110
  });
92
111
  }
93
112
  const selectLicenseeLabel = /*#__PURE__*/React.createElement("div", {
@@ -135,7 +154,7 @@ const StoreModalProductContent = _ref => {
135
154
  // styles – but stays in the product state, so selecting its siblings can
136
155
  // still upgrade to it (an unlisted superfamily) or to a collection bundle
137
156
  // containing it (an unlisted variable family).
138
- const selectableFamilies = [collection, ...(collection.children ?? [])].filter(family => family.visibility !== 'UNLISTED');
157
+ const selectableFamilies = collection.__typename === 'FontCollection' ? [collection, ...(collection.children ?? [])].filter(family => family.visibility !== 'UNLISTED') : [];
139
158
  return /*#__PURE__*/React.createElement(StoreModalPageContainer, null, {
140
159
  body: /*#__PURE__*/React.createElement(Fragment, null, licensesPosition === 'top' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(StoreModalLicenseeIsBillingSelection, {
141
160
  disabled: false
@@ -151,14 +170,18 @@ const StoreModalProductContent = _ref => {
151
170
  }, {
152
171
  label: selectOrderVariablesLabel
153
172
  }), /*#__PURE__*/React.createElement(StoreModalProductLicenseSelection, {
154
- collection: collection,
173
+ licenseable: collection,
155
174
  disabled: !hasSelectionForLicenseeIsBillingIdentity || Boolean(requiredOrderVariablesNotYetSelected)
156
175
  }, {
157
176
  label: selectLicensesLabel
158
177
  })), /*#__PURE__*/React.createElement("div", {
159
178
  className: "store-modal__families",
160
179
  "data-disabled": licensesPosition === 'top' ? !anyLicenseOptionsSelected || !hasSelectionForLicenseeIsBillingIdentity || !!requiredOrderVariablesNotYetSelected : false
161
- }, selectableFamilies.map((family, index) => /*#__PURE__*/React.createElement(StoreModalFamily, {
180
+ }, collection.__typename === 'FontPack' ? /*#__PURE__*/React.createElement(StoreModalPack, {
181
+ title: selectFontsLabel,
182
+ pack: collection,
183
+ onSelect: handleSelectProduct
184
+ }) : selectableFamilies.map((family, index) => /*#__PURE__*/React.createElement(StoreModalFamily, {
162
185
  key: family.id,
163
186
  title: index === 0 ? selectFontsLabel : undefined,
164
187
  collection: family,
@@ -177,7 +200,7 @@ const StoreModalProductContent = _ref => {
177
200
  }, {
178
201
  label: selectOrderVariablesLabel
179
202
  }), /*#__PURE__*/React.createElement(StoreModalProductLicenseSelection, {
180
- collection: collection,
203
+ licenseable: collection,
181
204
  disabled: !anySkusSelected || !hasSelectionForLicenseeIsBillingIdentity || Boolean(requiredOrderVariablesNotYetSelected)
182
205
  }, {
183
206
  label: selectLicensesLabel
@@ -201,13 +224,13 @@ function StoreModalProductByIdRenderer(_ref2) {
201
224
  });
202
225
  }
203
226
  function StoreModalProductBySlugRenderer(_ref3) {
204
- var _data$viewer, _data$viewer$slug;
227
+ var _data$viewer, _data$viewer$slug, _data$viewer2, _data$viewer2$slug;
205
228
  let {
206
229
  prepared
207
230
  } = _ref3;
208
231
  const data = usePreloadedQuery(productSlugQuery, prepared.productQuery);
209
232
  return /*#__PURE__*/React.createElement(StoreModalProductContent, {
210
- collection: (_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$slug = _data$viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.collection,
233
+ collection: ((_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : (_data$viewer$slug = _data$viewer.slug) === null || _data$viewer$slug === void 0 ? void 0 : _data$viewer$slug.collection) ?? ((_data$viewer2 = data.viewer) === null || _data$viewer2 === void 0 ? void 0 : (_data$viewer2$slug = _data$viewer2.slug) === null || _data$viewer2$slug === void 0 ? void 0 : _data$viewer2$slug.pack),
211
234
  viewer: data.viewer
212
235
  });
213
236
  }
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { StoreModalProductLicenseSelection_collection$key } from '__generated__/StoreModalProductLicenseSelection_collection.graphql.js';
2
+ import { StoreModalProductLicenseSelection_licenseable$key } from '__generated__/StoreModalProductLicenseSelection_licenseable.graphql.js';
3
3
  interface StoreModalProductLicenseSelectionProps {
4
- collection: StoreModalProductLicenseSelection_collection$key;
4
+ licenseable: StoreModalProductLicenseSelection_licenseable$key;
5
5
  disabled: boolean;
6
6
  children: {
7
7
  label: React.ReactNode;
8
8
  };
9
9
  }
10
- export default function StoreModalProductLicenseSelection({ collection: collectionKey, disabled, children: { label }, }: StoreModalProductLicenseSelectionProps): React.JSX.Element;
10
+ export default function StoreModalProductLicenseSelection({ licenseable: licenseableKey, disabled, children: { label }, }: StoreModalProductLicenseSelectionProps): React.JSX.Element;
11
11
  export {};
@@ -1,17 +1,17 @@
1
- import _StoreModalProductLicenseSelection_collection from "../../__generated__/StoreModalProductLicenseSelection_collection.graphql.js";
1
+ import _StoreModalProductLicenseSelection_licenseable from "../../__generated__/StoreModalProductLicenseSelection_licenseable.graphql.js";
2
2
  import React from 'react';
3
3
  import { graphql, useFragment } from 'react-relay';
4
4
  import StoreModalProductLicense from './StoreModalProductLicense.js';
5
5
  export default function StoreModalProductLicenseSelection(_ref) {
6
- var _collection$licenses;
6
+ var _licenseable$licenses;
7
7
  let {
8
- collection: collectionKey,
8
+ licenseable: licenseableKey,
9
9
  disabled,
10
10
  children: {
11
11
  label
12
12
  }
13
13
  } = _ref;
14
- const collection = useFragment((_StoreModalProductLicenseSelection_collection.hash && _StoreModalProductLicenseSelection_collection.hash !== "c3609e849ae8057e71542d32716f3ddc" && console.error("The definition of 'StoreModalProductLicenseSelection_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductLicenseSelection_collection), collectionKey);
14
+ const licenseable = useFragment((_StoreModalProductLicenseSelection_licenseable.hash && _StoreModalProductLicenseSelection_licenseable.hash !== "4016af73fd5c8c03fa7bcdfba6ce0b9b" && console.error("The definition of 'StoreModalProductLicenseSelection_licenseable' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductLicenseSelection_licenseable), licenseableKey);
15
15
  return /*#__PURE__*/React.createElement("div", {
16
16
  className: "store-modal__license-selection__container",
17
17
  "data-disabled": disabled
@@ -19,9 +19,9 @@ export default function StoreModalProductLicenseSelection(_ref) {
19
19
  className: "store-modal__license-selection__title"
20
20
  }, label), /*#__PURE__*/React.createElement("div", {
21
21
  className: "store-modal__license-selection__licenses"
22
- }, collection === null || collection === void 0 ? void 0 : (_collection$licenses = collection.licenses) === null || _collection$licenses === void 0 ? void 0 : _collection$licenses.map(license => license && /*#__PURE__*/React.createElement(StoreModalProductLicense, {
22
+ }, licenseable === null || licenseable === void 0 ? void 0 : (_licenseable$licenses = licenseable.licenses) === null || _licenseable$licenses === void 0 ? void 0 : _licenseable$licenses.map(license => license && /*#__PURE__*/React.createElement(StoreModalProductLicense, {
23
23
  key: license.id,
24
24
  license: license,
25
- collectionId: collection.id
25
+ collectionId: licenseable.id
26
26
  }))));
27
27
  }
@@ -57,7 +57,7 @@ const StoreModalProductSummary = _ref => {
57
57
  const refetchVariables = useMemo(() => ({
58
58
  selectedSkuIds: selectedSkuIdsArray
59
59
  }), [selectedSkuIdsArray]);
60
- const [viewer, refetch] = useRefetchableFragment((_StoreModalProductSummary_viewer.hash && _StoreModalProductSummary_viewer.hash !== "81a5f120d76cbb84753d158a7ff38c19" && console.error("The definition of 'StoreModalProductSummary_viewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductSummary_viewer), viewerKey);
60
+ const [viewer, refetch] = useRefetchableFragment((_StoreModalProductSummary_viewer.hash && _StoreModalProductSummary_viewer.hash !== "afd13ac5f41519eea722d5138b244b25" && console.error("The definition of 'StoreModalProductSummary_viewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _StoreModalProductSummary_viewer), viewerKey);
61
61
  useRefetchOnLicenseChanges({
62
62
  environment,
63
63
  refetch,
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  interface StoreModalFamilyProps {
3
3
  children: {
4
4
  title: React.ReactNode;
5
- familyButton: React.ReactElement<any>;
5
+ familyButton?: React.ReactElement<any> | null;
6
6
  bundles?: React.ReactElement<any>[] | null;
7
7
  styleGroups?: React.ReactElement<any>[][] | null;
8
8
  };
@@ -12,7 +12,7 @@ const StoreModalFamily = _ref => {
12
12
  className: "store-modal__family__container"
13
13
  }, title && /*#__PURE__*/React.createElement("h3", {
14
14
  className: "store-modal__family__title"
15
- }, title), /*#__PURE__*/React.cloneElement(familyButton, {
15
+ }, title), familyButton && /*#__PURE__*/React.cloneElement(familyButton, {
16
16
  className: 'store-modal__family__family-button'
17
17
  }), bundles === null || bundles === void 0 ? void 0 : bundles.map(bundle => /*#__PURE__*/React.cloneElement(bundle, {
18
18
  className: 'store-modal__family__bundle-button'
package/dist/fontdue.css CHANGED
@@ -2017,6 +2017,12 @@ input:checked + .checkbox__icon {
2017
2017
  display: none;
2018
2018
  }
2019
2019
 
2020
+ .store-modal__family-button__styles-price {
2021
+ color: var(--secondary_text_color);
2022
+ text-decoration: line-through;
2023
+ margin-right: 0.5em;
2024
+ }
2025
+
2020
2026
  .store-modal__family-button__name {
2021
2027
  font-size: 32px;
2022
2028
  line-height: normal;
package/dist/reducer.d.ts CHANGED
@@ -28,6 +28,10 @@ export interface FontdueState {
28
28
  collectionSkus: {
29
29
  [collectionSkuId: string]: string | null;
30
30
  };
31
+ productSkuIds: {
32
+ [productId: string]: string[];
33
+ };
34
+ viewedProductId: string | null;
31
35
  skuPrices: {
32
36
  [collectionSkuId: string]: number;
33
37
  };
@@ -88,6 +92,9 @@ export type FontdueAction = {
88
92
  variables?: object;
89
93
  } | {
90
94
  type: 'STORE_MODAL_BACK';
95
+ } | {
96
+ type: 'STORE_MODAL_VIEW_PRODUCT';
97
+ productId: string | null;
91
98
  } | {
92
99
  type: 'UPDATE_ORDER_VARIABLE_SELECTIONS';
93
100
  selections: OrderVariableSelectionState[];
package/dist/reducer.js CHANGED
@@ -157,6 +157,13 @@ const reducer = (state, action) => {
157
157
  return storeModalReplace(state, action);
158
158
  case 'STORE_MODAL_BACK':
159
159
  return storeModalBack(state);
160
+ // The product page being viewed bounds what a pick on it may sit inside
161
+ // (selection.ts), so a change of page re-prices the selection.
162
+ case 'STORE_MODAL_VIEW_PRODUCT':
163
+ return state.viewedProductId === action.productId ? state : repriceSelection({
164
+ ...state,
165
+ viewedProductId: action.productId
166
+ });
160
167
  case 'UPDATE_ORDER_VARIABLE_SELECTIONS':
161
168
  return updateOrderVariableSelections(state, action);
162
169
  case 'UPDATE_LICENSEE_IS_BILLING_IDENTITY':
@@ -188,6 +195,8 @@ export function createDefaultStore(config) {
188
195
  skuStyleIds: {},
189
196
  skuPrices: {},
190
197
  collectionSkus: {},
198
+ productSkuIds: {},
199
+ viewedProductId: null,
191
200
  fetchedCollectionIds: [],
192
201
  storeModalRoute: initialRoute,
193
202
  storeModalHistory: [],
@@ -1,5 +1,9 @@
1
1
  import type { FontdueState } from './reducer.js';
2
2
  type Catalog = Pick<FontdueState, 'skuStyleIds' | 'skuPrices'>;
3
+ interface ViewedProduct {
4
+ id: string;
5
+ skuIds: ReadonlySet<string>;
6
+ }
3
7
  /**
4
8
  * The cheapest set of SKUs in which each of `picks` sits inside one SKU
5
9
  * granting all of its styles, sorted. Picks whose styles or price aren't known
@@ -9,7 +13,7 @@ type Catalog = Pick<FontdueState, 'skuStyleIds' | 'skuPrices'>;
9
13
  * cheapestSkuIds(['sku:bold', 'sku:regular', 'sku:light'], catalog)
10
14
  * // => ['sku:light+regular+bold'] rather than a pair plus a single
11
15
  */
12
- export declare const cheapestSkuIds: (picks: readonly string[], catalog: Catalog) => string[];
16
+ export declare const cheapestSkuIds: (picks: readonly string[], catalog: Catalog, viewed?: ViewedProduct | null) => string[];
13
17
  /**
14
18
  * Re-derives `selectedSkuIds` from the picks. A pick whose styles or price
15
19
  * aren't known yet – selected from the host page before its product data
package/dist/selection.js CHANGED
@@ -19,8 +19,14 @@
19
19
  * wins, then the one with the fewest SKUs – so a variable font comes along with
20
20
  * a collection bundle priced like its static family, and a family beats its two
21
21
  * halves at the same price.
22
+ *
23
+ * On a product page, a pick the page shows only sits inside SKUs the page shows
24
+ * (see `viewedProduct`).
22
25
  */
23
26
 
27
+ // The product page being viewed and every SKU its fetch returned – what the
28
+ // page renders.
29
+
24
30
  // Nodes the exact search may visit per part of the catalog before it settles
25
31
  // for the best combination found so far. Real catalogs finish far below it; it
26
32
  // only bounds pathological ones.
@@ -159,6 +165,23 @@ const containersOf = (index, pick) => {
159
165
  return containers;
160
166
  };
161
167
 
168
+ // The containers a pick may sit inside. Every product the buyer has seen – or
169
+ // has a BuyButton for on the host page – stays in the catalog, so on a product
170
+ // page a pick the page shows only sits inside SKUs the page shows. Otherwise a
171
+ // font pack seen earlier would take a member family's styles on the family's
172
+ // own page, with a SKU that page doesn't show (even a family sold only by the
173
+ // style), and a family seen earlier would take the pack page's partial block
174
+ // of its styles (FD-79). A SKU granting exactly the pick's styles at its price
175
+ // is the pick as far as the index goes – only one of two such twins is
176
+ // offered – so it always stays.
177
+ const holdersOf = (index, pick, viewed) => {
178
+ const containers = containersOf(index, pick);
179
+ if (!(viewed !== null && viewed !== void 0 && viewed.skuIds.has(pick))) return containers;
180
+ const size = index.granted.get(pick).size;
181
+ const price = index.catalog.skuPrices[pick];
182
+ return containers.filter(skuId => viewed.skuIds.has(skuId) || index.granted.get(skuId).size === size && index.catalog.skuPrices[skuId] === price);
183
+ };
184
+
162
185
  // The parts a pick's styles belong to.
163
186
  const partsOf = (index, pick) => {
164
187
  const cached = index.parts.get(pick);
@@ -199,27 +222,30 @@ const notNull = value => value !== null;
199
222
  * cheapestSkuIds(['sku:bold', 'sku:regular', 'sku:light'], catalog)
200
223
  * // => ['sku:light+regular+bold'] rather than a pair plus a single
201
224
  */
202
- export const cheapestSkuIds = (picks, catalog) => cheapestCombination(picks, catalog).skuIds;
203
- const selectionCost = (picks, catalog) => cheapestCombination(picks, catalog).cost;
204
- const cheapestCombination = (picks, catalog) => {
225
+ export const cheapestSkuIds = function (picks, catalog) {
226
+ let viewed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
227
+ return cheapestCombination(picks, catalog, viewed).skuIds;
228
+ };
229
+ const selectionCost = (picks, catalog, viewed) => cheapestCombination(picks, catalog, viewed).cost;
230
+ const cheapestCombination = (picks, catalog, viewed) => {
205
231
  const index = indexFor(catalog);
206
232
  const priceable = [...new Set(picks)].filter(pick => index.granted.has(pick)).sort();
207
- return cover(priceable, new Set(), index) ?? EMPTY_COVER;
233
+ return cover(priceable, new Set(), index, viewed) ?? EMPTY_COVER;
208
234
  };
209
235
 
210
236
  // The cheapest cover of `picks` (sorted) without the `excluded` bridges, or
211
237
  // null when some pick has nowhere to sit without them.
212
- const cover = (picks, excluded, index) => {
238
+ const cover = (picks, excluded, index, viewed) => {
213
239
  if (picks.length === 0) return EMPTY_COVER;
214
- const key = `${picks.join(',')}#${[...excluded].sort().join(',')}`;
240
+ const key = `${picks.join(',')}#${[...excluded].sort().join(',')}@${(viewed === null || viewed === void 0 ? void 0 : viewed.id) ?? ''}`;
215
241
  if (index.covers.has(key)) return index.covers.get(key);
216
- const result = coverUncached(picks, excluded, index);
242
+ const result = coverUncached(picks, excluded, index, viewed);
217
243
  if (index.covers.size >= MEMO_LIMIT) index.covers.clear();
218
244
  index.covers.set(key, result);
219
245
  return result;
220
246
  };
221
- const coverUncached = (picks, excluded, index) => {
222
- const available = new Map(picks.map(pick => [pick, containersOf(index, pick).filter(id => !excluded.has(id))]));
247
+ const coverUncached = (picks, excluded, index, viewed) => {
248
+ const available = new Map(picks.map(pick => [pick, holdersOf(index, pick, viewed).filter(id => !excluded.has(id))]));
223
249
  if (picks.some(pick => available.get(pick).length === 0)) return null;
224
250
  const parts = [...new Set(picks.flatMap(pick => [...partsOf(index, pick)]))];
225
251
  if (parts.length === 1) return branchAndBound(picks, available, index);
@@ -248,7 +274,7 @@ const coverUncached = (picks, excluded, index) => {
248
274
  const [part] = partsOf(index, pick);
249
275
  picksByGroup[groupOfPart.get(part)].push(pick);
250
276
  }
251
- const covers = picksByGroup.map(group => cover(group, excluded, index));
277
+ const covers = picksByGroup.map(group => cover(group, excluded, index, viewed));
252
278
  return covers.every(notNull) ? combine(covers) : null;
253
279
  }
254
280
 
@@ -258,13 +284,13 @@ const coverUncached = (picks, excluded, index) => {
258
284
  bridge
259
285
  } = joining[0];
260
286
  const withoutBridge = new Set([...excluded, bridge]);
261
- const rest = cover(picks.filter(pick => !available.get(pick).includes(bridge)), withoutBridge, index);
287
+ const rest = cover(picks.filter(pick => !available.get(pick).includes(bridge)), withoutBridge, index, viewed);
262
288
  const including = rest && combine([{
263
289
  skuIds: [bridge],
264
290
  cost: index.catalog.skuPrices[bridge],
265
291
  granted: index.granted.get(bridge)
266
292
  }, rest]);
267
- const excluding = cover(picks, withoutBridge, index);
293
+ const excluding = cover(picks, withoutBridge, index, viewed);
268
294
  if (!including || !excluding) return including ?? excluding;
269
295
  return isBetter(including, excluding) ? including : excluding;
270
296
  };
@@ -393,6 +419,23 @@ const greedyCover = (options, all) => {
393
419
  // Selection state
394
420
  // ---------------------------------------------------------------------------
395
421
 
422
+ const viewedSkuIdsCache = new WeakMap();
423
+
424
+ // The product page the Store Modal is on – as the page reports it, so one
425
+ // opened by slug counts the same as one opened by id – or null off a product
426
+ // page and while the product's fetch is still in flight. productState records
427
+ // every SKU a product's fetch returned, which is what its page renders.
428
+ const viewedProduct = state => {
429
+ const productId = state.viewedProductId;
430
+ const skuIds = productId ? state.productSkuIds[productId] : undefined;
431
+ if (!productId || !skuIds) return null;
432
+ if (!viewedSkuIdsCache.has(skuIds)) viewedSkuIdsCache.set(skuIds, new Set(skuIds));
433
+ return {
434
+ id: productId,
435
+ skuIds: viewedSkuIdsCache.get(skuIds)
436
+ };
437
+ };
438
+
396
439
  /**
397
440
  * Re-derives `selectedSkuIds` from the picks. A pick whose styles or price
398
441
  * aren't known yet – selected from the host page before its product data
@@ -400,7 +443,7 @@ const greedyCover = (options, all) => {
400
443
  */
401
444
  export const repriceSelection = state => {
402
445
  const unpriceable = state.requestedSkuIds.filter(skuId => !isPriceable(state, skuId));
403
- const skuIds = union(cheapestSkuIds(state.requestedSkuIds, state), unpriceable);
446
+ const skuIds = union(cheapestSkuIds(state.requestedSkuIds, state, viewedProduct(state)), unpriceable);
404
447
  const unchanged = selectedSkuIdList(state).length === skuIds.length && skuIds.every(skuId => state.selectedSkuIds[skuId]);
405
448
  return unchanged ? state : {
406
449
  ...state,
@@ -476,18 +519,20 @@ const costCache = new WeakMap();
476
519
  */
477
520
  export const additionalCost = (state, skuId) => {
478
521
  if (!isPriceable(state, skuId)) return null;
522
+ const viewed = viewedProduct(state);
479
523
  let cache = costCache.get(state.requestedSkuIds);
480
- if (!cache || cache.skuStyleIds !== state.skuStyleIds || cache.skuPrices !== state.skuPrices) {
524
+ if (!cache || cache.skuStyleIds !== state.skuStyleIds || cache.skuPrices !== state.skuPrices || cache.viewedId !== ((viewed === null || viewed === void 0 ? void 0 : viewed.id) ?? null)) {
481
525
  cache = {
482
526
  skuStyleIds: state.skuStyleIds,
483
527
  skuPrices: state.skuPrices,
484
- base: selectionCost(state.requestedSkuIds, state),
528
+ viewedId: (viewed === null || viewed === void 0 ? void 0 : viewed.id) ?? null,
529
+ base: selectionCost(state.requestedSkuIds, state, viewed),
485
530
  costs: new Map()
486
531
  };
487
532
  costCache.set(state.requestedSkuIds, cache);
488
533
  }
489
534
  if (!cache.costs.has(skuId)) {
490
- const withSku = selectionCost(union(state.requestedSkuIds, [skuId]), state);
535
+ const withSku = selectionCost(union(state.requestedSkuIds, [skuId]), state, viewed);
491
536
  cache.costs.set(skuId, withSku - cache.base);
492
537
  }
493
538
  return cache.costs.get(skuId);
@@ -28,10 +28,11 @@ function isGlobalId(value) {
28
28
  * `index` has to carry the configured `indexExcludeTags` — a route dispatched
29
29
  * without them lists collections the store front deliberately leaves out.
30
30
  *
31
- * A product is addressed by either its collection id or its slug, the same two
32
- * identifiers `BuyButton` takes. The route passes whichever it was given
33
- * through to the product query; a slug that names nothing (or names an article
34
- * rather than a collection) lands on the modal's "Collection not found".
31
+ * A product (a collection or a font pack) is addressed by either its id or its
32
+ * slug, the same two identifiers `BuyButton` takes. The route passes whichever
33
+ * it was given through to the product query; a slug that names nothing (or
34
+ * names an article rather than a product) lands on the modal's "Product not
35
+ * found".
35
36
  */
36
37
  const routes = {
37
38
  '^index$': config => ({
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.7.0";
11
+ export const version = "3.8.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",
@@ -1,25 +0,0 @@
1
- /**
2
- * @generated SignedSource<<2e0d1867d52ad59c939fc952ee3f136a>>
3
- * @lightSyntaxTransform
4
- * @nogrep
5
- */
6
- import { ReaderFragment } from 'relay-runtime';
7
- import { FragmentRefs } from "relay-runtime";
8
- export type BuyButton_collection$data = {
9
- readonly fontStyles: ReadonlyArray<{
10
- readonly sku: {
11
- readonly id: string;
12
- } | null;
13
- }>;
14
- readonly id: string;
15
- readonly sku: {
16
- readonly id: string;
17
- } | null;
18
- readonly " $fragmentType": "BuyButton_collection";
19
- };
20
- export type BuyButton_collection$key = {
21
- readonly " $data"?: BuyButton_collection$data;
22
- readonly " $fragmentSpreads": FragmentRefs<"BuyButton_collection">;
23
- };
24
- declare const node: ReaderFragment;
25
- export default node;
@@ -1,49 +0,0 @@
1
- /**
2
- * @generated SignedSource<<2e0d1867d52ad59c939fc952ee3f136a>>
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
- "storageKey": null
28
- };
29
- return {
30
- "argumentDefinitions": [],
31
- "kind": "Fragment",
32
- "metadata": null,
33
- "name": "BuyButton_collection",
34
- "selections": [v0 /*: any*/, v1 /*: any*/, {
35
- "alias": null,
36
- "args": null,
37
- "concreteType": "FontStyle",
38
- "kind": "LinkedField",
39
- "name": "fontStyles",
40
- "plural": true,
41
- "selections": [v1 /*: any*/],
42
- "storageKey": null
43
- }],
44
- "type": "FontCollection",
45
- "abstractKey": null
46
- };
47
- }();
48
- node.hash = "9c47030d7450494449d4aa5bda16e27e";
49
- export default node;