fontdue-js 3.8.0 → 3.10.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 (32) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -1
  3. package/dist/__generated__/StoreModalIndexItem_fontCollection.graphql.d.ts +10 -1
  4. package/dist/__generated__/StoreModalIndexItem_fontCollection.graphql.js +45 -35
  5. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.d.ts +1 -1
  6. package/dist/__generated__/StoreModalIndexPaginationQuery.graphql.js +101 -93
  7. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +3 -1
  8. package/dist/__generated__/StoreModalIndexQuery.graphql.js +24 -17
  9. package/dist/__generated__/TestFontsForm_Query.graphql.d.ts +14 -2
  10. package/dist/__generated__/TestFontsForm_Query.graphql.js +76 -18
  11. package/dist/__tests__/storeModalIndexUnlisted.test.js +71 -0
  12. package/dist/__tests__/testFontsFamilyChoice.test.js +150 -0
  13. package/dist/__tests__/typeTesterTruncate.test.js +71 -1
  14. package/dist/components/ConfigContext.d.ts +11 -0
  15. package/dist/components/ConfigContext.js +7 -1
  16. package/dist/components/StoreModal/StoreModalIndex.js +8 -2
  17. package/dist/components/StoreModal/StoreModalIndexItem.d.ts +1 -1
  18. package/dist/components/StoreModal/StoreModalIndexItem.js +26 -8
  19. package/dist/components/TestFontsForm/TestFontsFormElement.d.ts +4 -1
  20. package/dist/components/TestFontsForm/TestFontsFormElement.js +11 -3
  21. package/dist/components/TestFontsForm/index.d.ts +6 -3
  22. package/dist/components/TestFontsForm/index.js +82 -13
  23. package/dist/components/TestFontsForm/index.server.js +2 -1
  24. package/dist/components/TestFontsForm/variables.d.ts +8 -0
  25. package/dist/components/TestFontsForm/variables.js +22 -0
  26. package/dist/components/TypeTester/truncate.d.ts +10 -0
  27. package/dist/components/TypeTester/truncate.js +31 -0
  28. package/dist/components/TypeTester/useTypeTesterStyler.d.ts +1 -6
  29. package/dist/components/TypeTester/useTypeTesterStyler.js +22 -15
  30. package/dist/fontdue.css +22 -10
  31. package/dist/version.js +1 -1
  32. package/package.json +1 -1
@@ -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
+ };
@@ -10,3 +10,13 @@ export interface ResolvedTruncate {
10
10
  export declare const normalizeTruncate: (value?: TruncateOption) => ResolvedTruncate;
11
11
  export declare const shouldTruncate: ({ lines, expandOnFocus }: ResolvedTruncate, focused: boolean) => boolean;
12
12
  export declare const parseTruncateAttribute: (input: string | undefined, elementName: string) => TruncateOption | undefined;
13
+ export type ColumnsConfig = false | {
14
+ count: 'auto' | number;
15
+ width: string;
16
+ gap: string;
17
+ };
18
+ export declare const truncateColumnStyle: (columns: ColumnsConfig) => {
19
+ columnCount: "auto" | number;
20
+ columnWidth?: string;
21
+ columnGap?: string;
22
+ };
@@ -92,4 +92,35 @@ export const parseTruncateAttribute = (input, elementName) => {
92
92
  lines,
93
93
  expandOnFocus
94
94
  };
95
+ };
96
+ // The column declarations of a capped paragraph. Truncation lays the paragraph
97
+ // out as a single column whose overflow flows sideways, into clipped columns.
98
+ // With columns off that is a bare `column-count: 1`; WebKit before Safari 26.1
99
+ // builds no column for it, so the stylesheet adds the column-width those builds
100
+ // need (`[data-truncated]` in _type-tester.scss, FD-191). A configured single
101
+ // column with an auto width is written without its width for the same reason,
102
+ // so the stylesheet can reach it; the count is read loosely because a
103
+ // script-tag config arrives untyped. Everything else is written as configured,
104
+ // and an auto count keeps its auto width: 1px there would mean one column per
105
+ // pixel.
106
+ export const truncateColumnStyle = columns => {
107
+ if (!columns) return {
108
+ columnCount: 1
109
+ };
110
+ const {
111
+ count,
112
+ width,
113
+ gap
114
+ } = columns;
115
+ if (Number(count) === 1 && (!width || width === 'auto')) {
116
+ return {
117
+ columnCount: 1,
118
+ columnGap: gap
119
+ };
120
+ }
121
+ return {
122
+ columnCount: count,
123
+ columnWidth: width,
124
+ columnGap: gap
125
+ };
95
126
  };
@@ -2,12 +2,7 @@ import { EditorState } from 'draft-js';
2
2
  import { Alignment } from './types.js';
3
3
  import { VariableSettings } from '../../utils.js';
4
4
  import { VerticalMetrics } from '../useFontStyle.js';
5
- import { TruncateOption } from './truncate.js';
6
- export type ColumnsConfig = false | {
7
- count: 'auto' | number;
8
- width: string;
9
- gap: string;
10
- };
5
+ import { ColumnsConfig, TruncateOption } from './truncate.js';
11
6
  export interface UseTypeTesterStylerProps {
12
7
  autofit: boolean;
13
8
  autofitOnChange: boolean;
@@ -1,7 +1,7 @@
1
1
  import "../../global-shim.js";
2
2
  import { useLayoutEffect } from 'react';
3
3
  import useAutofit from '../../hooks/useAutofit.js';
4
- import { normalizeTruncate, shouldTruncate as resolveShouldTruncate } from './truncate.js';
4
+ import { normalizeTruncate, shouldTruncate as resolveShouldTruncate, truncateColumnStyle } from './truncate.js';
5
5
  const useTypeTesterStyler = _ref => {
6
6
  let {
7
7
  size,
@@ -86,12 +86,9 @@ const useTypeTesterStyler = _ref => {
86
86
  // is what lets the metric padding reveal descenders/ascenders cleanly, and it
87
87
  // means a multi-column tester (e.g. a 3-column specimen) truncates to
88
88
  // `truncateLines` lines *per column*. When truncating with columns disabled we
89
- // still establish a single column so the clipped overflow behaves the same way.
90
- const columnStyle = columns ? {
91
- columnCount: columns.count,
92
- columnWidth: columns.width,
93
- columnGap: columns.gap
94
- } : undefined;
89
+ // still establish a single column so the clipped overflow behaves the same way
90
+ // (truncateColumnStyle; WebKit before Safari 26.1 needs a stylesheet nudge to
91
+ // build that column, see `[data-truncated]` in _type-tester.scss, FD-191).
95
92
 
96
93
  // Outside truncation the columns apply to Draft's `[data-contents]` block
97
94
  // (via _type-tester.scss), which shrink-wraps — on the full-width container,
@@ -126,23 +123,35 @@ const useTypeTesterStyler = _ref => {
126
123
  // out to N lines of whitespace. Columns send the clipped overflow
127
124
  // sideways (into off-screen columns) rather than below, so the last
128
125
  // visible line of every column is never partially covered by the next
129
- // one. That lets the symmetric `lineOverflowEm` padding fully reveal the
130
- // first line's ascenders/accents and the last line's descenders at any
131
- // line-height; the negative margins cancel that padding in layout so a
126
+ // one. That lets a `lineOverflowEm` allowance above the first line and
127
+ // below the last fully reveal ascenders/accents and descenders at any
128
+ // line-height; the negative margins cancel both in layout so a
132
129
  // collapsed paragraph still occupies exactly its line count.
133
130
  //
131
+ // Where the two allowances go depends on the engine, so the stylesheet
132
+ // places them from the custom property below (`[data-truncated]` in
133
+ // _type-tester.scss); nothing here writes a padding inline, so those
134
+ // rules win. Chromium and Firefox fit a line into a column by its line
135
+ // box, so the column must span exactly N line boxes and the allowances
136
+ // are this box's own top and bottom padding, outside the column.
137
+ // WebKit fits a line by its inline box extent (the font's ascent plus
138
+ // descent): the Nth line would overhang such a column and move to the
139
+ // hidden one, and a line pushed into a later column gets a strut that
140
+ // starts it one allowance down. So for WebKit both allowances are
141
+ // padding on Draft's content block, inside the column, and every
142
+ // column holds the same N lines (FD-191).
143
+ //
134
144
  // Everything is in `em` so it tracks the rendered font-size (including
135
145
  // the mobile `--type-tester--adjustment`); the `+ 2px` is slack that
136
146
  // keeps sub-pixel rounding from dropping the Nth line (the extra never
137
147
  // reveals line N+1 because that line is clipped out to the side).
138
148
  boxSizing: 'border-box',
139
149
  maxHeight: `calc(${truncateLines * lineHeight + 2 * lineOverflowEm}em + 2px)`,
140
- paddingTop: `${lineOverflowEm}em`,
141
- paddingBottom: `${lineOverflowEm}em`,
142
150
  paddingLeft: sideBearing,
143
151
  paddingRight: sideBearing,
144
152
  marginTop: `${-lineOverflowEm}em`,
145
153
  marginBottom: `${-lineOverflowEm}em`,
154
+ '--type-tester--line-overflow': `${lineOverflowEm}em`,
146
155
  // `clip`, not `hidden`, once the cap outlives focus. A hidden box is
147
156
  // still a scroll container, so the browser scrolls it to chase a
148
157
  // caret past line N, and the offset is unrecoverable because there is
@@ -167,9 +176,7 @@ const useTypeTesterStyler = _ref => {
167
176
  columnFill: 'auto',
168
177
  widows: 1,
169
178
  orphans: 1,
170
- ...(columnStyle ?? {
171
- columnCount: 1
172
- })
179
+ ...truncateColumnStyle(columns)
173
180
  } : {
174
181
  height: 'auto',
175
182
  overflow: 'visible',
package/dist/fontdue.css CHANGED
@@ -182,6 +182,9 @@
182
182
  .test-fonts__checkbox-field label p {
183
183
  margin: 0;
184
184
  }
185
+ .test-fonts__families .test-fonts__checkbox-field {
186
+ margin-bottom: 10px;
187
+ }
185
188
  .test-fonts__agreement[data-highlighted=true] {
186
189
  color: red;
187
190
  }
@@ -721,8 +724,6 @@ fontdue-type-tester {
721
724
  }
722
725
  .type-tester__features__button {
723
726
  display: flex;
724
- --checkbox-size: 20px;
725
- --checkbox-margin: 10px;
726
727
  }
727
728
  .type-tester__features[data-selection-style=checkbox] .type-tester__features__button {
728
729
  margin-bottom: 5px;
@@ -889,6 +890,19 @@ fontdue-type-tester {
889
890
  }
890
891
  .type-tester__text__container[data-truncated] {
891
892
  overflow: hidden;
893
+ padding-top: var(--type-tester--line-overflow, 0);
894
+ padding-bottom: var(--type-tester--line-overflow, 0);
895
+ }
896
+ @supports (font: -apple-system-body) or (-webkit-hyphenate-limit-lines: 1) {
897
+ .type-tester__text__container[data-truncated] {
898
+ column-width: 1px;
899
+ padding-top: 0;
900
+ padding-bottom: 0;
901
+ }
902
+ .type-tester__text__container[data-truncated] [data-contents=true] {
903
+ padding-top: var(--type-tester--line-overflow, 0);
904
+ padding-bottom: var(--type-tester--line-overflow, 0);
905
+ }
892
906
  }
893
907
  .type-tester__text__container [data-contents=true] {
894
908
  column-count: var(--type-tester--column-count, auto);
@@ -1293,8 +1307,6 @@ fontdue-feature-testers {
1293
1307
  grid-template-columns: max-content auto;
1294
1308
  grid-column-gap: 20px;
1295
1309
  line-height: 1.6;
1296
- --checkbox-size: 20px;
1297
- --checkbox-margin: 10px;
1298
1310
  }
1299
1311
  .character-viewer__monitor__details > span {
1300
1312
  white-space: nowrap;
@@ -1375,10 +1387,10 @@ fontdue-feature-testers {
1375
1387
  .checkbox {
1376
1388
  position: relative;
1377
1389
  flex-shrink: 0;
1378
- margin-right: var(--checkbox-margin, 20px);
1379
- width: var(--checkbox-size, 40px);
1380
- height: var(--checkbox-size, 40px);
1381
- font-size: calc(var(--checkbox-size, 40px) * 0.8);
1390
+ margin-right: var(--checkbox-margin, 10px);
1391
+ width: var(--checkbox-size, 20px);
1392
+ height: var(--checkbox-size, 20px);
1393
+ font-size: calc(var(--checkbox-size, 20px) * 0.8);
1382
1394
  }
1383
1395
  .checkbox input {
1384
1396
  background: none;
@@ -1402,8 +1414,8 @@ fontdue-feature-testers {
1402
1414
  .checkbox input {
1403
1415
  -webkit-appearance: none;
1404
1416
  vertical-align: top;
1405
- width: var(--checkbox-size, 40px);
1406
- height: var(--checkbox-size, 40px);
1417
+ width: var(--checkbox-size, 20px);
1418
+ height: var(--checkbox-size, 20px);
1407
1419
  border: 1px solid var(--checkbox-color, currentcolor);
1408
1420
  }
1409
1421
  .checkbox__icon {
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.10.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",