fontdue-js 3.12.1 → 3.14.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 (30) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/__generated__/StoreModalFamily_collection.graphql.d.ts +2 -1
  3. package/dist/__generated__/StoreModalFamily_collection.graphql.js +14 -4
  4. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  5. package/dist/__generated__/StoreModalProductQuery.graphql.js +11 -7
  6. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  7. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +11 -7
  8. package/dist/__generated__/StoreModalProductSlugQuery.graphql.d.ts +1 -1
  9. package/dist/__generated__/StoreModalProductSlugQuery.graphql.js +11 -7
  10. package/dist/__generated__/productState_Query.graphql.d.ts +1 -29
  11. package/dist/__generated__/productState_Query.graphql.js +15 -30
  12. package/dist/__generated__/productState_collection.graphql.d.ts +1 -1
  13. package/dist/__generated__/productState_collection.graphql.js +8 -4
  14. package/dist/__generated__/productState_fontPack.graphql.d.ts +1 -1
  15. package/dist/__generated__/productState_fontPack.graphql.js +7 -3
  16. package/dist/__generated__/productState_node.graphql.d.ts +1 -29
  17. package/dist/__generated__/productState_node.graphql.js +8 -22
  18. package/dist/__tests__/bundleSelectionOrder.test.js +0 -1
  19. package/dist/__tests__/collectionBundleSelection.test.js +31 -6
  20. package/dist/__tests__/storeModalBundlePairs.test.js +142 -0
  21. package/dist/components/Root/productState.js +7 -33
  22. package/dist/components/StoreModal/StoreModalFamily.d.ts +3 -0
  23. package/dist/components/StoreModal/StoreModalFamily.js +20 -6
  24. package/dist/components/elements/StoreModalFamily/index.d.ts +1 -1
  25. package/dist/components/elements/StoreModalFamily/index.js +6 -3
  26. package/dist/fontdue.css +35 -0
  27. package/dist/version.js +1 -1
  28. package/package.json +1 -1
  29. package/dist/__generated__/productState_collectionBundle.graphql.d.ts +0 -41
  30. package/dist/__generated__/productState_collectionBundle.graphql.js +0 -81
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<89ccca0787788a2ceb37d58b6ab5340a>>
2
+ * @generated SignedSource<<7112b01c24002ecc9175f687500a7e12>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -60,13 +60,17 @@ const node = function () {
60
60
  },
61
61
  v4 = {
62
62
  "alias": null,
63
- "args": null,
63
+ "args": [{
64
+ "kind": "Literal",
65
+ "name": "acrossFamilies",
66
+ "value": true
67
+ }],
64
68
  "concreteType": "Bundle",
65
69
  "kind": "LinkedField",
66
70
  "name": "bundles",
67
71
  "plural": true,
68
72
  "selections": [v0 /*: any*/, v1 /*: any*/, v2 /*: any*/, v3 /*: any*/],
69
- "storageKey": null
73
+ "storageKey": "bundles(acrossFamilies:true)"
70
74
  };
71
75
  return {
72
76
  "argumentDefinitions": [],
@@ -76,24 +80,6 @@ const node = function () {
76
80
  },
77
81
  "name": "productState_node",
78
82
  "selections": [v0 /*: any*/, v1 /*: any*/, v2 /*: any*/, v3 /*: any*/, v4 /*: any*/, {
79
- "alias": null,
80
- "args": null,
81
- "concreteType": "CollectionBundle",
82
- "kind": "LinkedField",
83
- "name": "collectionBundles",
84
- "plural": true,
85
- "selections": [v0 /*: any*/, v1 /*: any*/, v2 /*: any*/, {
86
- "alias": null,
87
- "args": null,
88
- "concreteType": "FontCollection",
89
- "kind": "LinkedField",
90
- "name": "memberCollections",
91
- "plural": true,
92
- "selections": [v0 /*: any*/, v2 /*: any*/, v3 /*: any*/],
93
- "storageKey": null
94
- }],
95
- "storageKey": null
96
- }, {
97
83
  "alias": null,
98
84
  "args": [{
99
85
  "kind": "Literal",
@@ -111,5 +97,5 @@ const node = function () {
111
97
  "abstractKey": null
112
98
  };
113
99
  }();
114
- node.hash = "a1e172db45ca550a254819d877497d12";
100
+ node.hash = "aab1ef07e0192a0d22a4c300084d9d05";
115
101
  export default node;
@@ -42,7 +42,6 @@ const catalog = {
42
42
  fontStyles: styles.map(style)
43
43
  };
44
44
  }),
45
- collectionBundles: [],
46
45
  children: []
47
46
  }
48
47
  };
@@ -506,6 +506,16 @@ describe('POPULATE_PRODUCT_STATE', () => {
506
506
  fontStyles: [style('vf1', null)],
507
507
  bundles: []
508
508
  };
509
+ const display = {
510
+ id: 'display',
511
+ name: 'Display',
512
+ sku: sku('display-sku', 500),
513
+ fontStyles: [style('d1', 'sd1')],
514
+ bundles: []
515
+ };
516
+ // The superfamily's own bundles, read with `bundles(acrossFamilies: true)`:
517
+ // whole families (what collection bundles were), and styles from two
518
+ // families. The server expands a family member into its styles.
509
519
  const superfamily = {
510
520
  node: {
511
521
  __typename: 'FontCollection',
@@ -513,14 +523,18 @@ describe('POPULATE_PRODUCT_STATE', () => {
513
523
  name: 'Thunder',
514
524
  sku: sku('super-sku', 750),
515
525
  fontStyles: [],
516
- bundles: [],
517
- collectionBundles: [{
526
+ bundles: [{
518
527
  id: 'text-bundle',
519
528
  name: 'Text + Variable',
520
529
  sku: sku('text-bundle-sku', 500),
521
- memberCollections: [textFamily, textVariable]
530
+ fontStyles: [...textFamily.fontStyles, ...textVariable.fontStyles]
531
+ }, {
532
+ id: 'regulars',
533
+ name: 'Regulars',
534
+ sku: sku('regulars-sku', 60),
535
+ fontStyles: [style('f1', 's1'), style('d1', 'sd1')]
522
536
  }],
523
- children: [textFamily, textVariable]
537
+ children: [textFamily, textVariable, display]
524
538
  }
525
539
  };
526
540
  it('records the styles every SKU grants', () => {
@@ -529,14 +543,25 @@ describe('POPULATE_PRODUCT_STATE', () => {
529
543
  data: superfamily
530
544
  });
531
545
  expect(state.skuStyleIds).toEqual({
532
- 'super-sku': ['f1', 'f2', 'vf1'],
546
+ 'super-sku': ['f1', 'f2', 'vf1', 'd1'],
533
547
  'text-bundle-sku': ['f1', 'f2', 'vf1'],
548
+ 'regulars-sku': ['f1', 'd1'],
534
549
  'text-sku': ['f1', 'f2'],
535
550
  'text-var-sku': ['vf1'],
551
+ 'display-sku': ['d1'],
536
552
  'upright-sku': ['f1'],
537
553
  s1: ['f1'],
538
- s2: ['f2']
554
+ s2: ['f2'],
555
+ sd1: ['d1']
556
+ });
557
+ });
558
+ it('rolls styles from two families up to a superfamily bundle', () => {
559
+ const loaded = reducer(makeState(), {
560
+ type: 'POPULATE_PRODUCT_STATE',
561
+ data: superfamily
539
562
  });
563
+ const state = select(select(loaded, 's1'), 'sd1');
564
+ expect(selected(state)).toEqual(['regulars-sku']);
540
565
  });
541
566
  it('prices a SKU picked before its product loaded once it has', () => {
542
567
  const picked = select(makeState(), 'text-sku');
@@ -0,0 +1,142 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ // Bundles sit side by side when their names make a pair ("Thin" directly
4
+ // followed by "Thin Italic"), the way a style sits beside its italic.
5
+
6
+ import React from 'react';
7
+ import { renderToStaticMarkup } from 'react-dom/server';
8
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
9
+ const {
10
+ stateRef
11
+ } = vi.hoisted(() => ({
12
+ stateRef: {
13
+ current: null
14
+ }
15
+ }));
16
+
17
+ // The fragment reader hands back the data it was given; the redux selector
18
+ // runs against a default store's state.
19
+ vi.mock('react-relay', () => ({
20
+ graphql: () => null,
21
+ fetchQuery: vi.fn(),
22
+ useFragment: (_fragment, key) => key
23
+ }));
24
+ vi.mock('react-redux', async importOriginal => ({
25
+ ...(await importOriginal()),
26
+ useSelector: selector => selector(stateRef.current)
27
+ }));
28
+ import StoreModalFamily, { bundlesGrouped } from '../components/StoreModal/StoreModalFamily.js';
29
+ import { createDefaultStore } from '../reducer.js';
30
+ const grouped = function () {
31
+ for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) {
32
+ names[_key] = arguments[_key];
33
+ }
34
+ return bundlesGrouped(names.map(name => ({
35
+ name
36
+ }))).map(group => group.map(_ref => {
37
+ let {
38
+ name
39
+ } = _ref;
40
+ return name;
41
+ }));
42
+ };
43
+ describe('bundlesGrouped', () => {
44
+ it('pairs a bundle with a direct successor that repeats its name plus a suffix', () => {
45
+ expect(grouped('Thin', 'Thin Italic', 'Light', 'Light Oblique')).toEqual([['Thin', 'Thin Italic'], ['Light', 'Light Oblique']]);
46
+ });
47
+ it('accepts any suffix, not only italics', () => {
48
+ expect(grouped('Text', 'Text Alternates')).toEqual([['Text', 'Text Alternates']]);
49
+ });
50
+ it('only looks at the direct successor', () => {
51
+ expect(grouped('Thin', 'Light', 'Thin Italic')).toEqual([['Thin'], ['Light'], ['Thin Italic']]);
52
+ });
53
+ it('keeps exactly two: a third bundle with the same name splits them all', () => {
54
+ expect(grouped('Text', 'Text Italic', 'Text Oblique')).toEqual([['Text'], ['Text Italic'], ['Text Oblique']]);
55
+ });
56
+ it('wants a whole word, so a longer word is not a suffix', () => {
57
+ expect(grouped('Thin', 'Thinner')).toEqual([['Thin'], ['Thinner']]);
58
+ });
59
+ it('does not pair by order alone: the italic has to come second', () => {
60
+ expect(grouped('Thin Italic', 'Thin')).toEqual([['Thin Italic'], ['Thin']]);
61
+ });
62
+ it('ignores stray spaces around a name', () => {
63
+ expect(grouped('Semibold ', 'Semibold Italic')).toEqual([['Semibold ', 'Semibold Italic']]);
64
+ });
65
+ it('never pairs on an empty name', () => {
66
+ expect(grouped('', 'Thin')).toEqual([[''], ['Thin']]);
67
+ });
68
+ it('handles a missing list', () => {
69
+ expect(bundlesGrouped(null)).toEqual([]);
70
+ });
71
+ });
72
+ const money = amount => ({
73
+ amount,
74
+ currency: 'EUR'
75
+ });
76
+ const style = name => ({
77
+ id: `style:${name}`,
78
+ name,
79
+ supportedLanguages: ['en'],
80
+ variableInstances: [],
81
+ sku: {
82
+ id: `sku:style:${name}`,
83
+ price: money(4000)
84
+ },
85
+ cssFamily: 'Matex',
86
+ cssWeight: '400',
87
+ cssStretch: 'normal',
88
+ cssStyle: 'normal'
89
+ });
90
+ const bundle = function (name) {
91
+ let visibility = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'VISIBLE';
92
+ return {
93
+ id: `bundle:${name}`,
94
+ name,
95
+ visibility,
96
+ sku: {
97
+ id: `sku:bundle:${name}`,
98
+ price: money(26000)
99
+ },
100
+ fontStyles: [style(name)]
101
+ };
102
+ };
103
+ const collection = bundles => ({
104
+ __typename: 'FontCollection',
105
+ id: 'collection:matex',
106
+ name: 'HAL Matex',
107
+ collectionType: 'superfamily',
108
+ isVariableFont: false,
109
+ sku: {
110
+ id: 'sku:matex',
111
+ price: money(138667)
112
+ },
113
+ totalStyles: 60,
114
+ totalStylesPrice: money(138667),
115
+ featureStyle: null,
116
+ fontStyles: [],
117
+ bundles
118
+ });
119
+ describe('StoreModalFamily bundle rows', () => {
120
+ beforeEach(() => {
121
+ stateRef.current = createDefaultStore(undefined).getState();
122
+ });
123
+
124
+ // Bundle names, row by row.
125
+ const rows = bundles => {
126
+ const container = document.createElement('div');
127
+ container.innerHTML = renderToStaticMarkup( /*#__PURE__*/React.createElement(StoreModalFamily, {
128
+ collection: collection(bundles),
129
+ onSelect: () => {}
130
+ }));
131
+ return Array.from(container.querySelectorAll('.store-modal__family__group')).map(group => Array.from(group.querySelectorAll('.store-modal__family__bundle-button')).map(button => {
132
+ var _button$querySelector;
133
+ return (_button$querySelector = button.querySelector('.store-modal__style-button__style-name')) === null || _button$querySelector === void 0 ? void 0 : _button$querySelector.textContent;
134
+ }));
135
+ };
136
+ it('puts a pair on one row and leaves every other bundle on its own', () => {
137
+ expect(rows([bundle('Thin'), bundle('Thin Italic'), bundle('Complete')])).toEqual([['Thin', 'Thin Italic'], ['Complete']]);
138
+ });
139
+ it('pairs what is listed: an unlisted bundle between two names has no button', () => {
140
+ expect(rows([bundle('Thin'), bundle('Secret', 'UNLISTED'), bundle('Thin Italic')])).toEqual([['Thin', 'Thin Italic']]);
141
+ });
142
+ });
@@ -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
  };
@@ -12,5 +12,8 @@ interface GroupableStyle {
12
12
  cssStyle: string | null;
13
13
  }
14
14
  export declare function stylesGrouped<T extends GroupableStyle>(fontStyles: ReadonlyArray<T> | null | undefined): ReadonlyArray<ReadonlyArray<T>>;
15
+ export declare function bundlesGrouped<T extends {
16
+ name: string;
17
+ }>(bundles: ReadonlyArray<T> | null | undefined): ReadonlyArray<ReadonlyArray<T>>;
15
18
  export default function StoreModalFamily({ collection: collectionKey, onSelect, title, }: StoreModalFamilyProps): React.JSX.Element | null;
16
19
  export {};
@@ -33,6 +33,16 @@ export function stylesGrouped(fontStyles) {
33
33
  return slantedCount === 1 ? [chunk] : chunk.map(style => [style]);
34
34
  });
35
35
  }
36
+
37
+ // Pairs a bundle with its direct successor when that one's name repeats it plus
38
+ // a suffix ("Thin" then "Thin Italic"), so the two render side by side like a
39
+ // style beside its italic. Names are all there is to go on: a bundle's styles
40
+ // can come from several families. As with styles, only exactly two stay paired.
41
+ export function bundlesGrouped(bundles) {
42
+ if (!bundles) return [];
43
+ const chunks = chunkConsecutiveBy(bundles, (left, right) => right.name.trim().startsWith(`${left.name.trim()} `));
44
+ return chunks.flatMap(chunk => chunk.length === 2 ? [chunk] : chunk.map(b => [b]));
45
+ }
36
46
  export default function StoreModalFamily(_ref) {
37
47
  var _collection$bundles;
38
48
  let {
@@ -40,22 +50,26 @@ export default function StoreModalFamily(_ref) {
40
50
  onSelect,
41
51
  title
42
52
  } = _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);
53
+ const collection = useFragment((_StoreModalFamily_collection.hash && _StoreModalFamily_collection.hash !== "ed59465ca68bf3d3278773a671ddefb3" && 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
54
  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;
55
+ // An unlisted bundle gets no button, but its price still applies as an
56
+ // upgrade: the product state reads every bundle.
57
+ const listedBundles = (_collection$bundles = collection.bundles) === null || _collection$bundles === void 0 ? void 0 : _collection$bundles.filter(bundle => bundle.visibility !== 'UNLISTED');
58
+
59
+ // A superfamily sold only through its families has nothing of its own to
60
+ // show – unless one of its bundles, across its families, has a button.
61
+ 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
62
  return /*#__PURE__*/React.createElement(FamilyElement, null, {
47
63
  title,
48
64
  familyButton: /*#__PURE__*/React.createElement(FamilyButton, {
49
65
  collection: collection,
50
66
  onSelectSku: onSelect
51
67
  }),
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, {
68
+ bundleGroups: bundlesGrouped(listedBundles).map(group => group.map(bundle => /*#__PURE__*/React.createElement(StoreModalBundleButton, {
55
69
  key: bundle.id,
56
70
  bundle: bundle,
57
71
  onSelectSku: onSelect
58
- })),
72
+ }))),
59
73
  styleGroups: fontStyles.map(group => group.map(fontStyle => /*#__PURE__*/React.createElement(StyleButton, {
60
74
  key: fontStyle.id,
61
75
  fontStyle: fontStyle,
@@ -3,7 +3,7 @@ interface StoreModalFamilyProps {
3
3
  children: {
4
4
  title: React.ReactNode;
5
5
  familyButton?: React.ReactElement<any> | null;
6
- bundles?: React.ReactElement<any>[] | null;
6
+ bundleGroups?: React.ReactElement<any>[][] | null;
7
7
  styleGroups?: React.ReactElement<any>[][] | null;
8
8
  };
9
9
  }
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  const StoreModalFamily = _ref => {
3
3
  let {
4
4
  children: {
5
- bundles,
5
+ bundleGroups,
6
6
  familyButton,
7
7
  styleGroups,
8
8
  title
@@ -14,9 +14,12 @@ const StoreModalFamily = _ref => {
14
14
  className: "store-modal__family__title"
15
15
  }, title), familyButton && /*#__PURE__*/React.cloneElement(familyButton, {
16
16
  className: 'store-modal__family__family-button'
17
- }), bundles === null || bundles === void 0 ? void 0 : bundles.map(bundle => /*#__PURE__*/React.cloneElement(bundle, {
17
+ }), bundleGroups === null || bundleGroups === void 0 ? void 0 : bundleGroups.map((group, i) => /*#__PURE__*/React.createElement("div", {
18
+ className: "store-modal__family__group",
19
+ key: i
20
+ }, group.map(bundleButton => /*#__PURE__*/React.cloneElement(bundleButton, {
18
21
  className: 'store-modal__family__bundle-button'
19
- })), styleGroups === null || styleGroups === void 0 ? void 0 : styleGroups.map((group, i) => /*#__PURE__*/React.createElement("div", {
22
+ })))), styleGroups === null || styleGroups === void 0 ? void 0 : styleGroups.map((group, i) => /*#__PURE__*/React.createElement("div", {
20
23
  className: "store-modal__family__group",
21
24
  key: i
22
25
  }, group.map(styleButton => /*#__PURE__*/React.cloneElement(styleButton, {
package/dist/fontdue.css CHANGED
@@ -127,6 +127,7 @@
127
127
  display: block;
128
128
  width: 1em;
129
129
  height: 1em;
130
+ max-width: none;
130
131
  fill: currentColor;
131
132
  }
132
133
 
@@ -1784,6 +1785,7 @@ input:checked + .checkbox__icon {
1784
1785
 
1785
1786
  .store-modal__container__buttons {
1786
1787
  text-align: right;
1788
+ white-space: nowrap;
1787
1789
  }
1788
1790
 
1789
1791
  .store-modal__container__back-button {
@@ -2136,6 +2138,14 @@ input:checked + .checkbox__icon {
2136
2138
  font-size: 60px;
2137
2139
  }
2138
2140
 
2141
+ .store-modal__index-item__description:first-child {
2142
+ grid-column: 1/-2;
2143
+ }
2144
+
2145
+ .store-modal__index-item__buy {
2146
+ grid-column: -2/-1;
2147
+ }
2148
+
2139
2149
  .store-modal__index-item__name {
2140
2150
  font-size: 20px;
2141
2151
  line-height: 30px;
@@ -2693,6 +2703,31 @@ input:checked + .checkbox__icon {
2693
2703
  .store-modal__family__bundle-button .store-modal__style-button__price {
2694
2704
  align-self: flex-end;
2695
2705
  }
2706
+ .store-modal__family__bundle-button .store-modal__style-button__checked {
2707
+ height: 1lh;
2708
+ }
2709
+ }
2710
+ .store-modal__family__bundle-button {
2711
+ container-type: inline-size;
2712
+ }
2713
+
2714
+ @container (max-width: 480px) {
2715
+ .store-modal__family__bundle-button .store-modal__style-button__container {
2716
+ flex-wrap: wrap;
2717
+ }
2718
+ .store-modal__family__bundle-button .store-modal__style-button__feature-glyphs {
2719
+ flex: 0 0 100%;
2720
+ }
2721
+ .store-modal__family__bundle-button .store-modal__style-button__style-name {
2722
+ flex: 1 1 0;
2723
+ min-width: 0;
2724
+ }
2725
+ .store-modal__family__bundle-button .store-modal__style-button__price {
2726
+ align-self: flex-end;
2727
+ }
2728
+ .store-modal__family__bundle-button .store-modal__style-button__checked {
2729
+ height: 1lh;
2730
+ }
2696
2731
  }
2697
2732
  body[data-fontdue-store-modal=open] {
2698
2733
  overflow: hidden;
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.1";
11
+ export const version = "3.14.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.12.1",
3
+ "version": "3.14.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;