fontdue-js 3.8.0 → 3.9.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 3.9.0
2
+
3
+ - `TestFontsForm` can let the visitor choose which families to download. With *Customer’s choice* set under *Settings → Test fonts* – or `testFonts: { familySelection: "choice" }` in the config, or the `familySelection` prop (`family-selection` on `<fontdue-test-fonts-form>`) – the form lists the families with a checkbox each plus *Select all*, and the download holds only the ticked ones. Ships new stylesheet rules, so take `dist/fontdue.css` from this version alongside the JS.
4
+ - `TestFontsForm` takes `collectionSlug` or `collectionId` (`collection-slug` / `collection-id`, comma-separated for several) to offer just those families, with no checkboxes – for a form on a family’s own page. When preloading, pass the same value to `loadTestFontsFormQuery({ collectionSlug })`; the old options-only call still works.
5
+
1
6
  ## 3.8.0
2
7
 
3
8
  - Font packs. `StoreModal` lists a store’s packs at the top of its browse page and opens one as a product page, with the same price bar, license picker, cart line and checkout a collection gets. `BuyButton` takes `fontPackId` or `fontPackSlug` in place of `collectionId`/`collectionSlug` – `font-pack-id` or `font-pack-slug` on `<fontdue-buy-button>` – and `?store=product/<pack-slug>` opens a pack directly. Packs honour `indexExcludeTags` the way collections do.
package/README.md CHANGED
@@ -842,10 +842,12 @@ import TestFontsForm, { loadTestFontsFormQuery } from "fontdue-js/TestFontsForm"
842
842
 
843
843
  | Prop | Description |
844
844
  | --- | --- |
845
- | `preloadedQuery` | (Optional, SSR) Result of `loadTestFontsFormQuery()`. Lazy if omitted. |
845
+ | `preloadedQuery` | (Optional, SSR) Result of `loadTestFontsFormQuery({ collectionSlug, collectionId })` – pass the same collection props as the form. Lazy if omitted. |
846
846
  | `agreementLabel` | (Optional) `string` Label for the required agreement checkbox. Defaults to the "EULA agreement text" field in your Fontdue Labels settings. |
847
847
  | `downloadLabel` | (Optional) `string` Submit button label. Defaults to `"Download test fonts"`. |
848
848
  | `newsletterCheckboxChecked` | (Optional) `boolean` Pre-check the newsletter opt-in. |
849
+ | `collectionSlug` / `collectionId` | (Optional) `string \| string[]` Limit the download to these font collections (a superfamily stands for its families). No family checkboxes are shown. |
850
+ | `familySelection` | (Optional) `'all' \| 'choice'` Overrides `config.testFonts.familySelection` and the dashboard's *Families in each download* setting. `'choice'` lists the families as checkboxes. |
849
851
 
850
852
  ## `NewsletterSignup`
851
853
 
@@ -1,12 +1,20 @@
1
1
  /**
2
- * @generated SignedSource<<ee5a584fb3013fee58346747b616f67d>>
2
+ * @generated SignedSource<<47f718aeae16846330759690c693dce6>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
6
6
  import { ConcreteRequest } from 'relay-runtime';
7
- export type TestFontsForm_Query$variables = Record<PropertyKey, never>;
7
+ export type TestFontsForm_Query$variables = {
8
+ collectionIds?: ReadonlyArray<string> | null;
9
+ collectionSlugs?: ReadonlyArray<string> | null;
10
+ };
8
11
  export type TestFontsForm_Query$data = {
9
12
  readonly viewer: {
13
+ readonly fontdueConfig: {
14
+ readonly testFonts: {
15
+ readonly familySelection: string;
16
+ };
17
+ } | null;
10
18
  readonly settings: {
11
19
  readonly eulaAgreementLabel: string | null;
12
20
  readonly newsletterOptInLabel: string | null;
@@ -15,6 +23,10 @@ export type TestFontsForm_Query$data = {
15
23
  } | null;
16
24
  readonly testFonts: {
17
25
  readonly archiveUrl: string | null;
26
+ readonly families: ReadonlyArray<{
27
+ readonly id: string;
28
+ readonly name: string;
29
+ }>;
18
30
  } | null;
19
31
  };
20
32
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @generated SignedSource<<ee5a584fb3013fee58346747b616f67d>>
2
+ * @generated SignedSource<<47f718aeae16846330759690c693dce6>>
3
3
  * @lightSyntaxTransform
4
4
  * @nogrep
5
5
  */
@@ -10,11 +10,20 @@
10
10
 
11
11
  const node = function () {
12
12
  var v0 = [{
13
+ "defaultValue": null,
14
+ "kind": "LocalArgument",
15
+ "name": "collectionIds"
16
+ }, {
17
+ "defaultValue": null,
18
+ "kind": "LocalArgument",
19
+ "name": "collectionSlugs"
20
+ }],
21
+ v1 = [{
13
22
  "kind": "Literal",
14
23
  "name": "format",
15
24
  "value": "HTML"
16
25
  }],
17
- v1 = {
26
+ v2 = {
18
27
  "alias": null,
19
28
  "args": null,
20
29
  "concreteType": "Settings",
@@ -23,13 +32,13 @@ const node = function () {
23
32
  "plural": false,
24
33
  "selections": [{
25
34
  "alias": null,
26
- "args": v0 /*: any*/,
35
+ "args": v1 /*: any*/,
27
36
  "kind": "ScalarField",
28
37
  "name": "eulaAgreementLabel",
29
38
  "storageKey": "eulaAgreementLabel(format:\"HTML\")"
30
39
  }, {
31
40
  "alias": null,
32
- "args": v0 /*: any*/,
41
+ "args": v1 /*: any*/,
33
42
  "kind": "ScalarField",
34
43
  "name": "newsletterOptInLabel",
35
44
  "storageKey": "newsletterOptInLabel(format:\"HTML\")"
@@ -48,9 +57,49 @@ const node = function () {
48
57
  }],
49
58
  "storageKey": null
50
59
  },
51
- v2 = {
60
+ v3 = {
61
+ "alias": null,
62
+ "args": null,
63
+ "concreteType": "FontdueConfig",
64
+ "kind": "LinkedField",
65
+ "name": "fontdueConfig",
66
+ "plural": false,
67
+ "selections": [{
68
+ "alias": null,
69
+ "args": null,
70
+ "concreteType": "FontdueTestFontsConfig",
71
+ "kind": "LinkedField",
72
+ "name": "testFonts",
73
+ "plural": false,
74
+ "selections": [{
75
+ "alias": null,
76
+ "args": null,
77
+ "kind": "ScalarField",
78
+ "name": "familySelection",
79
+ "storageKey": null
80
+ }],
81
+ "storageKey": null
82
+ }],
83
+ "storageKey": null
84
+ },
85
+ v4 = {
52
86
  "alias": null,
53
87
  "args": null,
88
+ "kind": "ScalarField",
89
+ "name": "id",
90
+ "storageKey": null
91
+ },
92
+ v5 = {
93
+ "alias": null,
94
+ "args": [{
95
+ "kind": "Variable",
96
+ "name": "collectionIds",
97
+ "variableName": "collectionIds"
98
+ }, {
99
+ "kind": "Variable",
100
+ "name": "collectionSlugs",
101
+ "variableName": "collectionSlugs"
102
+ }],
54
103
  "concreteType": "TestFonts",
55
104
  "kind": "LinkedField",
56
105
  "name": "testFonts",
@@ -61,12 +110,27 @@ const node = function () {
61
110
  "kind": "ScalarField",
62
111
  "name": "archiveUrl",
63
112
  "storageKey": null
113
+ }, {
114
+ "alias": null,
115
+ "args": null,
116
+ "concreteType": "FontCollection",
117
+ "kind": "LinkedField",
118
+ "name": "families",
119
+ "plural": true,
120
+ "selections": [v4 /*: any*/, {
121
+ "alias": null,
122
+ "args": null,
123
+ "kind": "ScalarField",
124
+ "name": "name",
125
+ "storageKey": null
126
+ }],
127
+ "storageKey": null
64
128
  }],
65
129
  "storageKey": null
66
130
  };
67
131
  return {
68
132
  "fragment": {
69
- "argumentDefinitions": [],
133
+ "argumentDefinitions": v0 /*: any*/,
70
134
  "kind": "Fragment",
71
135
  "metadata": null,
72
136
  "name": "TestFontsForm_Query",
@@ -77,7 +141,7 @@ const node = function () {
77
141
  "kind": "LinkedField",
78
142
  "name": "viewer",
79
143
  "plural": false,
80
- "selections": [v1 /*: any*/, v2 /*: any*/],
144
+ "selections": [v2 /*: any*/, v3 /*: any*/, v5 /*: any*/],
81
145
  "storageKey": null
82
146
  }],
83
147
  "type": "RootQueryType",
@@ -85,7 +149,7 @@ const node = function () {
85
149
  },
86
150
  "kind": "Request",
87
151
  "operation": {
88
- "argumentDefinitions": [],
152
+ "argumentDefinitions": v0 /*: any*/,
89
153
  "kind": "Operation",
90
154
  "name": "TestFontsForm_Query",
91
155
  "selections": [{
@@ -95,25 +159,19 @@ const node = function () {
95
159
  "kind": "LinkedField",
96
160
  "name": "viewer",
97
161
  "plural": false,
98
- "selections": [v1 /*: any*/, v2 /*: any*/, {
99
- "alias": null,
100
- "args": null,
101
- "kind": "ScalarField",
102
- "name": "id",
103
- "storageKey": null
104
- }],
162
+ "selections": [v2 /*: any*/, v3 /*: any*/, v5 /*: any*/, v4 /*: any*/],
105
163
  "storageKey": null
106
164
  }]
107
165
  },
108
166
  "params": {
109
- "cacheID": "328cab794a68cbe666374e901da4d26b",
167
+ "cacheID": "42a58842afe113b89a0bc969738246a8",
110
168
  "id": null,
111
169
  "metadata": {},
112
170
  "name": "TestFontsForm_Query",
113
171
  "operationKind": "query",
114
- "text": "query TestFontsForm_Query {\n viewer {\n settings {\n eulaAgreementLabel(format: HTML)\n newsletterOptInLabel(format: HTML)\n recaptchaEnabled\n recaptchaSiteKey\n }\n testFonts {\n archiveUrl\n }\n id\n }\n}\n"
172
+ "text": "query TestFontsForm_Query(\n $collectionIds: [ID!]\n $collectionSlugs: [String!]\n) {\n viewer {\n settings {\n eulaAgreementLabel(format: HTML)\n newsletterOptInLabel(format: HTML)\n recaptchaEnabled\n recaptchaSiteKey\n }\n fontdueConfig {\n testFonts {\n familySelection\n }\n }\n testFonts(collectionIds: $collectionIds, collectionSlugs: $collectionSlugs) {\n archiveUrl\n families {\n id\n name\n }\n }\n id\n }\n}\n"
115
173
  }
116
174
  };
117
175
  }();
118
- node.hash = "cd43f0cacc4dcf01cf94fb1ff97197ca";
176
+ node.hash = "da75e312a602ac0ee2dfb79271299c77";
119
177
  export default node;
@@ -0,0 +1,150 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ // The test fonts form's family choice (FD-28): the archive endpoint takes
4
+ // `collection_ids[]` to narrow the ZIP, and the form fills it from checkboxes
5
+ // (`familySelection: 'choice'`) or from its own `collectionId`/`collectionSlug`.
6
+
7
+ import React, { act } from 'react';
8
+ import { createRoot } from 'react-dom/client';
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
10
+ const {
11
+ queryRef
12
+ } = vi.hoisted(() => ({
13
+ queryRef: {
14
+ data: null,
15
+ variables: null
16
+ }
17
+ }));
18
+ vi.mock('react-relay', () => ({
19
+ graphql: () => null,
20
+ commitMutation: vi.fn(),
21
+ useRelayEnvironment: () => ({}),
22
+ usePreloadedQuery: () => queryRef.data,
23
+ useLazyLoadQuery: (_query, variables) => {
24
+ queryRef.variables = variables;
25
+ return queryRef.data;
26
+ }
27
+ }));
28
+ vi.mock('../components/FontdueContextProvider/index.js', () => ({
29
+ EnsureFontdueContext: _ref => {
30
+ let {
31
+ children
32
+ } = _ref;
33
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
34
+ },
35
+ LazyQueryBoundary: _ref2 => {
36
+ let {
37
+ children
38
+ } = _ref2;
39
+ return /*#__PURE__*/React.createElement(React.Fragment, null, children);
40
+ }
41
+ }));
42
+ import TestFontsForm from '../components/TestFontsForm/index.js';
43
+ import ConfigContext, { makeConfig } from '../components/ConfigContext.js';
44
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
45
+ const families = [{
46
+ id: 'RmFtaWx5OjE=',
47
+ name: 'Alpha'
48
+ }, {
49
+ id: 'RmFtaWx5OjI=',
50
+ name: 'Beta'
51
+ }];
52
+ const viewer = function (familySelection) {
53
+ let list = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : families;
54
+ return {
55
+ viewer: {
56
+ settings: {
57
+ eulaAgreementLabel: 'I agree',
58
+ newsletterOptInLabel: null,
59
+ recaptchaEnabled: false,
60
+ recaptchaSiteKey: null
61
+ },
62
+ fontdueConfig: {
63
+ testFonts: {
64
+ familySelection
65
+ }
66
+ },
67
+ testFonts: {
68
+ archiveUrl: 'https://example.test/test-fonts/archive',
69
+ families: list
70
+ }
71
+ }
72
+ };
73
+ };
74
+ let container;
75
+ let root;
76
+ beforeEach(() => {
77
+ container = document.createElement('div');
78
+ document.body.append(container);
79
+ root = createRoot(container);
80
+ });
81
+ afterEach(() => {
82
+ act(() => root.unmount());
83
+ container.remove();
84
+ });
85
+ const render = function () {
86
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
87
+ let config = arguments.length > 1 ? arguments[1] : undefined;
88
+ return act(() => root.render( /*#__PURE__*/React.createElement(ConfigContext.Provider, {
89
+ value: makeConfig(config)
90
+ }, /*#__PURE__*/React.createElement(TestFontsForm, props))));
91
+ };
92
+ const familyCheckboxes = () => [...container.querySelectorAll('.test-fonts__family input')];
93
+ const hiddenIds = () => [...container.querySelectorAll('input[type="hidden"]')].filter(input => input.name === 'collection_ids[]').map(input => input.value);
94
+ describe('TestFontsForm family choice', () => {
95
+ it('downloads everything without checkboxes by default', () => {
96
+ queryRef.data = viewer('all');
97
+ render();
98
+ expect(familyCheckboxes()).toHaveLength(0);
99
+ expect(hiddenIds()).toEqual([]);
100
+ expect(queryRef.variables).toEqual({
101
+ collectionIds: null,
102
+ collectionSlugs: null
103
+ });
104
+ });
105
+ it('lists the families when the dashboard says choice, and sends the ticked ones', () => {
106
+ queryRef.data = viewer('choice');
107
+ render();
108
+ expect(familyCheckboxes().map(box => box.value)).toEqual(families.map(f => f.id));
109
+ expect(container.textContent).toContain('Beta');
110
+ expect(hiddenIds()).toEqual([]);
111
+ act(() => familyCheckboxes()[1].click());
112
+ expect(hiddenIds()).toEqual(['RmFtaWx5OjI=']);
113
+ act(() => container.querySelector('.test-fonts__families__all input').click());
114
+ expect(hiddenIds()).toEqual(families.map(f => f.id));
115
+ });
116
+ it('lets code config and the prop override the dashboard setting', () => {
117
+ queryRef.data = viewer('choice');
118
+ render({}, {
119
+ testFonts: {
120
+ familySelection: 'all'
121
+ }
122
+ });
123
+ expect(familyCheckboxes()).toHaveLength(0);
124
+ queryRef.data = viewer('all');
125
+ render({
126
+ familySelection: 'choice'
127
+ });
128
+ expect(familyCheckboxes()).toHaveLength(2);
129
+ });
130
+ it('downloads exactly the named collections without checkboxes', () => {
131
+ queryRef.data = viewer('choice', [families[0]]);
132
+ render({
133
+ collectionSlug: 'alpha, gamma'
134
+ });
135
+ expect(queryRef.variables).toEqual({
136
+ collectionIds: null,
137
+ collectionSlugs: ['alpha', 'gamma']
138
+ });
139
+ expect(familyCheckboxes()).toHaveLength(0);
140
+ expect(hiddenIds()).toEqual(['RmFtaWx5OjE=']);
141
+ });
142
+ it('says so when the named collections have no test fonts', () => {
143
+ queryRef.data = viewer('all', []);
144
+ render({
145
+ collectionId: 'RmFtaWx5Ojk='
146
+ });
147
+ expect(container.querySelector('form')).toBeNull();
148
+ expect(container.textContent).toContain('Test fonts aren’t available for this family.');
149
+ });
150
+ });
@@ -28,8 +28,13 @@ interface PreviewConfig {
28
28
  revalidateEndpoint?: string;
29
29
  clientSide?: boolean;
30
30
  }
31
+ export type TestFontsFamilySelection = 'all' | 'choice';
32
+ interface TestFontsConfig {
33
+ familySelection?: TestFontsFamilySelection;
34
+ }
31
35
  export interface Config {
32
36
  form?: FormConfig;
37
+ testFonts?: TestFontsConfig;
33
38
  storeModal?: StoreModalConfig;
34
39
  typeTester?: TypeTesterConfig;
35
40
  stripe?: StripeConfig;
@@ -94,6 +99,9 @@ export declare const makeConfig: (config?: Config) => {
94
99
  form: {
95
100
  checkboxStyle: "check" | "cross";
96
101
  };
102
+ testFonts: {
103
+ familySelection: TestFontsFamilySelection | undefined;
104
+ };
97
105
  storeModal: {
98
106
  indexLayout: "styled-aa" | "styled-font-names";
99
107
  indexExcludeTags: string[] | undefined;
@@ -172,6 +180,9 @@ declare const _default: React.Context<{
172
180
  form: {
173
181
  checkboxStyle: "check" | "cross";
174
182
  };
183
+ testFonts: {
184
+ familySelection: TestFontsFamilySelection | undefined;
185
+ };
175
186
  storeModal: {
176
187
  indexLayout: "styled-aa" | "styled-font-names";
177
188
  indexExcludeTags: string[] | undefined;
@@ -88,12 +88,18 @@ export const mergeConfig = (base, override) => {
88
88
  return deepMerge(base, override);
89
89
  };
90
90
  export const makeConfig = config => {
91
- var _config$form, _config$storeModal, _config$storeModal2, _config$storeModal3, _config$storeModal4, _config$stripe, _config$tracking, _config$tracking2, _config$tracking3, _config$tracking4, _config$preview, _config$preview2, _config$preview3;
91
+ var _config$form, _config$testFonts, _config$storeModal, _config$storeModal2, _config$storeModal3, _config$storeModal4, _config$stripe, _config$tracking, _config$tracking2, _config$tracking3, _config$tracking4, _config$preview, _config$preview2, _config$preview3;
92
92
  return {
93
93
  typeTester: makeTypeTesterConfig(config === null || config === void 0 ? void 0 : config.typeTester),
94
94
  form: {
95
95
  checkboxStyle: (config === null || config === void 0 ? void 0 : (_config$form = config.form) === null || _config$form === void 0 ? void 0 : _config$form.checkboxStyle) ?? 'check'
96
96
  },
97
+ testFonts: {
98
+ // Deliberately no default: the form falls back to the dashboard setting
99
+ // it reads in its own query (the npm path never merges server config
100
+ // into `children`'s context – see ServerConfigProvider).
101
+ familySelection: config === null || config === void 0 ? void 0 : (_config$testFonts = config.testFonts) === null || _config$testFonts === void 0 ? void 0 : _config$testFonts.familySelection
102
+ },
97
103
  storeModal: {
98
104
  indexLayout: (config === null || config === void 0 ? void 0 : (_config$storeModal = config.storeModal) === null || _config$storeModal === void 0 ? void 0 : _config$storeModal.indexLayout) ?? 'styled-aa',
99
105
  indexExcludeTags: config === null || config === void 0 ? void 0 : (_config$storeModal2 = config.storeModal) === null || _config$storeModal2 === void 0 ? void 0 : _config$storeModal2.indexExcludeTags,
@@ -3,6 +3,9 @@ interface TestFontsFormElement_props {
3
3
  agreementLabel?: string;
4
4
  downloadLabel?: string;
5
5
  newsletterCheckboxChecked?: string;
6
+ collectionId?: string;
7
+ collectionSlug?: string;
8
+ familySelection?: string;
6
9
  }
7
- declare const TestFontsFormElement: (props: TestFontsFormElement_props) => React.JSX.Element;
10
+ declare const TestFontsFormElement: ({ familySelection, ...props }: TestFontsFormElement_props) => React.JSX.Element;
8
11
  export default TestFontsFormElement;
@@ -1,7 +1,15 @@
1
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
2
  import React from 'react';
3
3
  import TestFontsFormFetcher from './index.js';
4
- const TestFontsFormElement = props => /*#__PURE__*/React.createElement(TestFontsFormFetcher, _extends({}, props, {
5
- newsletterCheckboxChecked: props.newsletterCheckboxChecked === 'true' ? true : false
6
- }));
4
+ const FAMILY_SELECTIONS = ['all', 'choice'];
5
+ const TestFontsFormElement = _ref => {
6
+ let {
7
+ familySelection,
8
+ ...props
9
+ } = _ref;
10
+ return /*#__PURE__*/React.createElement(TestFontsFormFetcher, _extends({}, props, {
11
+ familySelection: familySelection && FAMILY_SELECTIONS.includes(familySelection) ? familySelection : undefined,
12
+ newsletterCheckboxChecked: props.newsletterCheckboxChecked === 'true' ? true : false
13
+ }));
14
+ };
7
15
  export default TestFontsFormElement;
@@ -1,14 +1,17 @@
1
1
  import React from 'react';
2
2
  import { TestFontsForm_Query } from '../../__generated__/TestFontsForm_Query.graphql.js';
3
3
  import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
4
- import { Config } from '../ConfigContext.js';
5
- export interface TestFontsForm_props {
4
+ import { Config, TestFontsFamilySelection } from '../ConfigContext.js';
5
+ import { TestFontsFormCollectionProps } from './variables.js';
6
+ export interface TestFontsForm_props extends TestFontsFormCollectionProps {
6
7
  agreementLabel?: string;
7
8
  downloadLabel?: string;
8
9
  newsletterCheckboxChecked?: boolean;
10
+ familySelection?: TestFontsFamilySelection;
9
11
  }
10
12
  export type TestFontsFormPreloadedQuery = SerializablePreloadedQuery<TestFontsForm_Query>;
11
- export declare function loadTestFontsFormQuery(options?: LoadQueryOptions): Promise<TestFontsFormPreloadedQuery>;
13
+ export type LoadTestFontsFormQueryVariables = TestFontsFormCollectionProps;
14
+ export declare function loadTestFontsFormQuery(variables?: LoadTestFontsFormQueryVariables | LoadQueryOptions, options?: LoadQueryOptions): Promise<TestFontsFormPreloadedQuery>;
12
15
  export declare function TestFontsFormPreloadedQueryRenderer({ preloadedQuery, ...rest }: TestFontsForm_props & {
13
16
  preloadedQuery: TestFontsFormPreloadedQuery;
14
17
  }): React.JSX.Element;
@@ -3,7 +3,7 @@
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
4
  import _TestFontsForm_Query from "../../__generated__/TestFontsForm_Query.graphql.js";
5
5
  import _TestFontsFormUpdateCustomerMutation from "../../__generated__/TestFontsFormUpdateCustomerMutation.graphql.js";
6
- import React, { useState, useRef, useCallback } from 'react';
6
+ import React, { useState, useRef, useCallback, useContext, useId } from 'react';
7
7
  import { commitMutation, graphql, useRelayEnvironment, useLazyLoadQuery, usePreloadedQuery } from 'react-relay';
8
8
  // The script-injecting reCAPTCHA wrapper (loads Google's api.js). Importing the
9
9
  // package's barebone named export instead was the FD-917 root cause; see
@@ -16,8 +16,18 @@ import Checkbox from '../Checkbox/index.js';
16
16
  import loadSerializableQuery from '../../relay/loadSerializableQuery.js';
17
17
  import useSerializablePreloadedQuery from '../../relay/useSerializablePreloadedQuery.js';
18
18
  import { EnsureFontdueContext, LazyQueryBoundary } from '../FontdueContextProvider/index.js';
19
+ import ConfigContext from '../ConfigContext.js';
20
+ import { testFontsFormQueryVariables } from './variables.js';
19
21
  import { useRecaptchaTimeout, isRecaptchaScriptLoaded, RECAPTCHA_UNAVAILABLE_MESSAGE } from '../../hooks/useRecaptchaTimeout.js';
20
22
  const updateCustomerMutation = (_TestFontsFormUpdateCustomerMutation.hash && _TestFontsFormUpdateCustomerMutation.hash !== "ba56958399f0893bd667ff02c33a6975" && console.error("The definition of 'TestFontsFormUpdateCustomerMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TestFontsFormUpdateCustomerMutation);
23
+ // The archive endpoint's family filter: `collection_ids[]` narrows the ZIP to
24
+ // those families; none means every family.
25
+ const COLLECTION_IDS_PARAM = 'collection_ids[]';
26
+ const withCollectionIds = (archiveUrl, collectionIds) => {
27
+ if (collectionIds.length === 0) return archiveUrl;
28
+ const query = new URLSearchParams(collectionIds.map(id => [COLLECTION_IDS_PARAM, id]));
29
+ return `${archiveUrl}${archiveUrl.includes('?') ? '&' : '?'}${query}`;
30
+ };
21
31
  const TestFontsDownloading = _ref => {
22
32
  let {
23
33
  archiveUrl
@@ -31,12 +41,15 @@ const TestFontsDownloading = _ref => {
31
41
  }, "here"), ".")));
32
42
  };
33
43
  const TestFontsFormComponent = _ref2 => {
34
- var _data$viewer$settings, _data$viewer$settings2, _data$viewer$testFont;
44
+ var _data$viewer$testFont, _data$viewer$fontdueC, _data$viewer$settings, _data$viewer$settings2, _data$viewer$testFont2;
35
45
  let {
36
46
  data,
37
47
  agreementLabel,
38
48
  downloadLabel = 'Download test fonts',
39
- newsletterCheckboxChecked = false
49
+ newsletterCheckboxChecked = false,
50
+ collectionId,
51
+ collectionSlug,
52
+ familySelection: familySelectionProp
40
53
  } = _ref2;
41
54
  const [email, setEmail] = useState('');
42
55
  const [name, setName] = useState('');
@@ -52,7 +65,28 @@ const TestFontsFormComponent = _ref2 => {
52
65
  const recaptchaRef = useRef(null);
53
66
  const recaptchaTimeout = useRecaptchaTimeout();
54
67
  const environment = useRelayEnvironment();
68
+ const config = useContext(ConfigContext);
69
+ const idPrefix = useId();
70
+ const [chosenIds, setChosenIds] = useState([]);
55
71
  if (!data.viewer) return null;
72
+ const families = ((_data$viewer$testFont = data.viewer.testFonts) === null || _data$viewer$testFont === void 0 ? void 0 : _data$viewer$testFont.families) ?? [];
73
+ const {
74
+ collectionIds,
75
+ collectionSlugs
76
+ } = testFontsFormQueryVariables({
77
+ collectionId,
78
+ collectionSlug
79
+ });
80
+ // A form for particular collections downloads exactly those families.
81
+ const limited = collectionIds != null || collectionSlugs != null;
82
+ // This form's prop, then code config (which, on the script-tag path, already
83
+ // has the dashboard setting merged beneath it), then the dashboard setting.
84
+ const familySelection = familySelectionProp ?? config.testFonts.familySelection ?? ((_data$viewer$fontdueC = data.viewer.fontdueConfig) === null || _data$viewer$fontdueC === void 0 ? void 0 : _data$viewer$fontdueC.testFonts.familySelection) ?? 'all';
85
+ // One family leaves nothing to choose.
86
+ const choosing = !limited && familySelection === 'choice' && families.length > 1;
87
+ const downloadIds = limited ? families.map(family => family.id) : choosing ? families.map(family => family.id).filter(id => chosenIds.includes(id)) : [];
88
+ const toggleFamily = (id, checked) => setChosenIds(ids => checked ? [...ids, id] : ids.filter(other => other !== id));
89
+ const allChosen = families.every(family => chosenIds.includes(family.id));
56
90
  const recaptchaEnabled = ((_data$viewer$settings = data.viewer.settings) === null || _data$viewer$settings === void 0 ? void 0 : _data$viewer$settings.recaptchaEnabled) ?? false;
57
91
  const recaptchaSiteKey = (_data$viewer$settings2 = data.viewer.settings) === null || _data$viewer$settings2 === void 0 ? void 0 : _data$viewer$settings2.recaptchaSiteKey;
58
92
  const submitForm = useCallback(token => {
@@ -151,23 +185,48 @@ const TestFontsFormComponent = _ref2 => {
151
185
  }
152
186
  submitForm(recaptchaToken);
153
187
  };
154
-
155
- // Button is disabled only while actively submitting or waiting for reCAPTCHA
156
- const disabled = !eulaAgreed || !email || !name;
188
+ const disabled = !eulaAgreed || !email || !name || choosing && downloadIds.length === 0;
157
189
  const settings = data.viewer.settings;
158
190
  const newsletterOptInLabel = settings === null || settings === void 0 ? void 0 : settings.newsletterOptInLabel;
159
191
  const eulaAgreementLabel = settings === null || settings === void 0 ? void 0 : settings.eulaAgreementLabel;
160
- const archiveUrl = (_data$viewer$testFont = data.viewer.testFonts) === null || _data$viewer$testFont === void 0 ? void 0 : _data$viewer$testFont.archiveUrl;
192
+ const archiveUrl = (_data$viewer$testFont2 = data.viewer.testFonts) === null || _data$viewer$testFont2 === void 0 ? void 0 : _data$viewer$testFont2.archiveUrl;
193
+ if (limited && families.length === 0) {
194
+ return /*#__PURE__*/React.createElement("div", {
195
+ className: "test-fonts test-fonts--unavailable"
196
+ }, /*#__PURE__*/React.createElement("p", null, "Test fonts aren\u2019t available for this family."));
197
+ }
161
198
  return /*#__PURE__*/React.createElement("div", {
162
199
  className: "test-fonts"
163
200
  }, error && /*#__PURE__*/React.createElement("div", {
164
201
  className: "test-fonts__errors"
165
202
  }, error), submitted ? /*#__PURE__*/React.createElement(TestFontsDownloading, {
166
- archiveUrl: archiveUrl
203
+ archiveUrl: withCollectionIds(archiveUrl, downloadIds)
167
204
  }) : /*#__PURE__*/React.createElement("form", {
168
205
  className: "test-fonts__form",
169
206
  onSubmit: handleSubmit
207
+ }, choosing && /*#__PURE__*/React.createElement("div", {
208
+ className: "test-fonts__section test-fonts__families",
209
+ role: "group",
210
+ "aria-label": "Families"
170
211
  }, /*#__PURE__*/React.createElement("div", {
212
+ className: "test-fonts__checkbox-field test-fonts__families__all"
213
+ }, /*#__PURE__*/React.createElement(Checkbox, {
214
+ id: `${idPrefix}-families-all`,
215
+ checked: allChosen,
216
+ onChange: event => setChosenIds(event.target.checked ? families.map(family => family.id) : [])
217
+ }), /*#__PURE__*/React.createElement("label", {
218
+ htmlFor: `${idPrefix}-families-all`
219
+ }, "Select all")), families.map((family, index) => /*#__PURE__*/React.createElement("div", {
220
+ className: "test-fonts__checkbox-field test-fonts__family",
221
+ key: family.id
222
+ }, /*#__PURE__*/React.createElement(Checkbox, {
223
+ id: `${idPrefix}-family-${index}`,
224
+ value: family.id,
225
+ checked: chosenIds.includes(family.id),
226
+ onChange: event => toggleFamily(family.id, event.target.checked)
227
+ }), /*#__PURE__*/React.createElement("label", {
228
+ htmlFor: `${idPrefix}-family-${index}`
229
+ }, family.name)))), /*#__PURE__*/React.createElement("div", {
171
230
  className: "test-fonts__section"
172
231
  }, /*#__PURE__*/React.createElement(TextField, {
173
232
  label: "Name *",
@@ -223,11 +282,21 @@ const TestFontsFormComponent = _ref2 => {
223
282
  }, " \u2192")))), /*#__PURE__*/React.createElement("form", {
224
283
  action: archiveUrl ?? undefined,
225
284
  ref: downloadForm
226
- }));
285
+ }, downloadIds.map(id => /*#__PURE__*/React.createElement("input", {
286
+ key: id,
287
+ type: "hidden",
288
+ name: COLLECTION_IDS_PARAM,
289
+ value: id
290
+ }))));
227
291
  };
228
- const query = (_TestFontsForm_Query.hash && _TestFontsForm_Query.hash !== "cd43f0cacc4dcf01cf94fb1ff97197ca" && console.error("The definition of 'TestFontsForm_Query' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TestFontsForm_Query);
229
- export async function loadTestFontsFormQuery(options) {
230
- return loadSerializableQuery(TestFontsFormQueryNode, {}, options);
292
+ const query = (_TestFontsForm_Query.hash && _TestFontsForm_Query.hash !== "da75e312a602ac0ee2dfb79271299c77" && console.error("The definition of 'TestFontsForm_Query' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TestFontsForm_Query);
293
+ // Pass the same `collectionId` / `collectionSlug` as the form's props.
294
+ export async function loadTestFontsFormQuery(variables, options) {
295
+ // Before the collection variables existed this took `options` alone.
296
+ if (variables && ('url' in variables || 'headers' in variables)) {
297
+ return loadTestFontsFormQuery({}, variables);
298
+ }
299
+ return loadSerializableQuery(TestFontsFormQueryNode, testFontsFormQueryVariables(variables ?? {}), options);
231
300
  }
232
301
  export function TestFontsFormPreloadedQueryRenderer(_ref4) {
233
302
  let {
@@ -244,7 +313,7 @@ export function TestFontsFormPreloadedQueryRenderer(_ref4) {
244
313
  }, rest));
245
314
  }
246
315
  function TestFontsFormLazyQueryRenderer(props) {
247
- const data = useLazyLoadQuery(query, {});
316
+ const data = useLazyLoadQuery(query, testFontsFormQueryVariables(props));
248
317
  return /*#__PURE__*/React.createElement(TestFontsFormComponent, _extends({
249
318
  data: data
250
319
  }, props));
@@ -2,6 +2,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
2
2
  import React from 'react';
3
3
  import loadSerializableQuery from '../../relay/loadSerializableQuery.js';
4
4
  import { TestFontsFormPreloadedQueryRenderer } from './index.js';
5
+ import { testFontsFormQueryVariables } from './variables.js';
5
6
  import TestFontsFormQueryNode from '../../__generated__/TestFontsForm_Query.graphql.js';
6
7
  // Stub for the RSC export condition. See FontdueProvider/index.server.tsx
7
8
  // for the rationale.
@@ -14,7 +15,7 @@ export function loadTestFontsFormQuery() {
14
15
  // `<TestFontsForm />` in an RSC without manually invoking
15
16
  // `loadTestFontsFormQuery` + threading `preloadedQuery` through props.
16
17
  export default async function TestFontsForm(props) {
17
- const preloadedQuery = await loadSerializableQuery(TestFontsFormQueryNode, {});
18
+ const preloadedQuery = await loadSerializableQuery(TestFontsFormQueryNode, testFontsFormQueryVariables(props));
18
19
  return /*#__PURE__*/React.createElement(TestFontsFormPreloadedQueryRenderer, _extends({
19
20
  preloadedQuery: preloadedQuery
20
21
  }, props));
@@ -0,0 +1,8 @@
1
+ export interface TestFontsFormCollectionProps {
2
+ collectionId?: string | readonly string[];
3
+ collectionSlug?: string | readonly string[];
4
+ }
5
+ export declare const testFontsFormQueryVariables: ({ collectionId, collectionSlug, }: TestFontsFormCollectionProps) => {
6
+ collectionIds: string[] | null;
7
+ collectionSlugs: string[] | null;
8
+ };
@@ -0,0 +1,22 @@
1
+ // Shared by the client component and the RSC entry (index.server.tsx), so it
2
+ // lives outside the 'use client' module: a server component can't call a
3
+ // function imported from one.
4
+
5
+ const toList = value => {
6
+ if (value == null) return null;
7
+ const list = (typeof value === 'string' ? value.split(',') : [...value]).map(item => item.trim()).filter(Boolean);
8
+ return list.length > 0 ? list : null;
9
+ };
10
+
11
+ // The query's variables for a form's collection props: null (no narrowing)
12
+ // unless the form is limited to particular collections.
13
+ export const testFontsFormQueryVariables = _ref => {
14
+ let {
15
+ collectionId,
16
+ collectionSlug
17
+ } = _ref;
18
+ return {
19
+ collectionIds: toList(collectionId),
20
+ collectionSlugs: toList(collectionSlug)
21
+ };
22
+ };
package/dist/fontdue.css CHANGED
@@ -182,6 +182,13 @@
182
182
  .test-fonts__checkbox-field label p {
183
183
  margin: 0;
184
184
  }
185
+ .test-fonts__families {
186
+ --checkbox-size: 20px;
187
+ --checkbox-margin: 10px;
188
+ }
189
+ .test-fonts__families .test-fonts__checkbox-field {
190
+ margin-bottom: 10px;
191
+ }
185
192
  .test-fonts__agreement[data-highlighted=true] {
186
193
  color: red;
187
194
  }
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.8.0";
11
+ export const version = "3.9.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",