fontdue-js 3.10.0 → 3.10.1

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,12 +1,16 @@
1
+ ## 3.10.1
2
+
3
+ - Fixed the `TypeTester`, `FeatureTesters`, `CharacterViewer` and `StoreModal` never rendering a font in some Safari versions when its family or style name contains punctuation, such as an apostrophe, or starts with a digit.
4
+
1
5
  ## 3.10.0
2
6
 
3
7
  - The `StoreModal`’s browse page leaves out an unlisted family, and lists an unlisted superfamily’s families in its place, each opening the superfamily’s page. Unlisted font packs are left out of the browse page too.
4
8
  - Checkboxes are 20px by default, down from 40px on the `TestFontsForm`, `NewsletterSignup` and license agreement checkboxes. Set `--checkbox-size: 40px` and `--checkbox-margin: 20px` to keep the larger box.
5
- - Fixed a type tester with a `truncate` line cap cutting through a line, or showing one line too few, in Safari before 26.1. The checkbox size and this fix ship as stylesheet rules, so take `dist/fontdue.css` from this version alongside the JS.
9
+ - Fixed a type tester with a `truncate` line cap cutting through a line, or showing one line too few, in Safari before 26.1.
6
10
 
7
11
  ## 3.9.0
8
12
 
9
- - `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.
13
+ - `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.
10
14
  - `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.
11
15
 
12
16
  ## 3.8.0
@@ -29,7 +33,7 @@
29
33
  - A product route takes a collection's slug as well as its ID, so `?store=product/example-sans` works alongside the base64 form. `fontdue-store-route="product/<slug>"` takes a slug now too – previously a slug there either did nothing (if it contained a hyphen) or loaded a broken product page.
30
34
  - `TypeTester` and `TypeTesters` take a `truncate` prop, and `<fontdue-type-tester>` and `<fontdue-type-testers>` take a `truncate` attribute, so each tester can cap its paragraph at its own number of lines instead of every tester on a site sharing one value. A per-tester value overrides the `truncate` UI config.
31
35
  - Fixed a type tester leaving a blank gap where its variable-axis sliders would go, on a static style or when none of the requested axes are in the font. The toolbar, features panel and dropdown placements were all affected.
32
- - A numeric `truncate` cap now stays in place while a visitor edits the text, instead of the paragraph jumping to full height on click and back on blur. `truncate: true` is unchanged and still opens to the full paragraph when clicked; pass `{ lines: 20, expandOnFocus: true }` if you want a line count to behave the old way. On the custom elements that object is a second word in the attribute – `truncate="20 expandable"`, or `truncate="20 fixed"` to spell out the default. Ships new stylesheet rules, so take `dist/fontdue.css` from this version alongside the JS.
36
+ - A numeric `truncate` cap now stays in place while a visitor edits the text, instead of the paragraph jumping to full height on click and back on blur. `truncate: true` is unchanged and still opens to the full paragraph when clicked; pass `{ lines: 20, expandOnFocus: true }` if you want a line count to behave the old way. On the custom elements that object is a second word in the attribute – `truncate="20 expandable"`, or `truncate="20 fixed"` to spell out the default.
33
37
  - Fixed a type tester opening at a size outside the range its size slider spans. A seeded size below `typeTester.size.min` (or above `max`) rendered at that size with the slider handle drawn off the end of its track; it is clamped into range now.
34
38
  - `NewsletterSignup` and `TestFontsForm` now name which form a submission came from, so Fontdue records each as its own event rather than as a checkout contact step.
35
39
  - Fixed the Store Modal’s Checkout button doing nothing on a store using Stripe Tax, and the Cart’s Next button on the billing and licensee steps. Stripe.js now throws “Unable to select session” when asked to validate a form that has no payment in it yet; the address fields are validated on their own in that case, with the same inline errors.
@@ -47,7 +51,7 @@
47
51
  ## 3.4.1
48
52
 
49
53
  - Fixed the standalone type tester dropping an axis slider when its `variable-settings` starting value is negative. `variable-settings="slnt -11"` was rejected as unparseable, so no slant slider appeared and nothing in the markup explained why. Any axis with a negative range was affected. Extra spaces between an axis and its value are tolerated now too.
50
- - Fixed the `FeatureTesters` switch knob rendering stretched in Safari 18 and earlier. The fix is in `dist/fontdue.css`.
54
+ - Fixed the `FeatureTesters` switch knob rendering stretched in Safari 18 and earlier.
51
55
 
52
56
  ## 3.4.0
53
57
 
@@ -74,7 +78,7 @@
74
78
 
75
79
  ## 3.2.3
76
80
 
77
- - Fixed `TypeTester` text drifting out of alignment. Centred, right-aligned and RTL testers sat off-centre once the font was small enough for the text to flow into columns, and centred testers were slightly off at every size. Ships new stylesheet rules, so take `fontdue-js/dist/fontdue.css` from this version alongside the JS.
81
+ - Fixed `TypeTester` text drifting out of alignment. Centred, right-aligned and RTL testers sat off-centre once the font was small enough for the text to flow into columns, and centred testers were slightly off at every size.
78
82
  - The collapsed `FontdueAdminToolbar` pill now carries the Fontdue wordmark — Fontdue, Fontdue · Previewing hidden fonts, Fontdue · Preview expired. If you style the toolbar yourself, `__mark` has left `__button` and the expired suffix is a new `__expired-suffix` span.
79
83
 
80
84
  ## 3.2.2
@@ -1,5 +1,37 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { selectFontSource } from "../fontLoader.js";
2
+ import { selectFontSource, fontStyleFamily } from "../fontLoader.js";
3
+ describe('fontStyleFamily', () => {
4
+ it('joins family and style unchanged when both are plain identifiers', () => {
5
+ expect(fontStyleFamily('Vain', 'Regular')).toBe('Vain Regular');
6
+ expect(fontStyleFamily('Sweet Lily', 'Bold Italic')).toBe('Sweet Lily Bold Italic');
7
+ });
8
+ it("escapes an apostrophe, which some Safari builds reject in a FontFace family", () => {
9
+ expect(fontStyleFamily("Hell's Gate", 'fairypools')).toBe('Hell_27s Gate fairypools');
10
+ });
11
+ it('escapes other ASCII punctuation instead of dropping it, so names stay distinct', () => {
12
+ expect(fontStyleFamily('A&B', 'Regular')).toBe('A_26B Regular');
13
+ expect(fontStyleFamily('A "B"', 'Regular')).toBe('A _22B_22 Regular');
14
+ expect(fontStyleFamily("Hell's Gate", 'Regular')).not.toBe(fontStyleFamily('Hells Gate', 'Regular'));
15
+ });
16
+ it('prefixes words CSS cannot parse unquoted: leading digits and reserved words', () => {
17
+ expect(fontStyleFamily('0712', 'Regular')).toBe('_0712 Regular');
18
+ expect(fontStyleFamily('Grotesk', '-5 Light')).toBe('Grotesk _-5 Light');
19
+ expect(fontStyleFamily('Initial', 'Bold')).toBe('_Initial Bold');
20
+ expect(fontStyleFamily('Default', 'Regular')).toBe('_Default Regular');
21
+ });
22
+ it('keeps non-ASCII letters, hyphens and underscores, and collapses whitespace', () => {
23
+ expect(fontStyleFamily('Généreuse Sans', 'Extra-Bold_2')).toBe('Généreuse Sans Extra-Bold_2');
24
+ expect(fontStyleFamily(' 游明朝 ', ' W3 ')).toBe('游明朝 W3');
25
+ });
26
+ it('only yields CSS identifiers', () => {
27
+ const names = [fontStyleFamily("Hell's Gate", 'fairypools'), fontStyleFamily('0712', '1'), fontStyleFamily('(Test) / 50%', 'Regular!'), fontStyleFamily('--x', 'y')];
28
+ for (const name of names) {
29
+ for (const word of name.split(' ')) {
30
+ expect(word).toMatch(/^(?:[A-Za-z_\u0080-￿]|-[A-Za-z_\u0080-￿])[\w\u0080-￿-]*$/);
31
+ }
32
+ }
33
+ });
34
+ });
3
35
  describe('selectFontSource', () => {
4
36
  const woff2 = function () {
5
37
  let extra = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -7,6 +7,7 @@ import { graphql, useFragment } from 'react-relay';
7
7
  import FontStyle from '../FontStyle/index.js';
8
8
  import useFeaturesData from '../TypeTester/useFeaturesData.js';
9
9
  import useFeatureTesterAutofit from '../../hooks/useFeatureTesterAutofit.js';
10
+ import { fontStyleFamily } from '../../fontLoader.js';
10
11
  import { sensitiveStringsForFeature, computeSensitivityFlags, segmentByFlags } from './highlight.js';
11
12
  const fragment = (_FeatureTesterCard_fontStyle.hash && _FeatureTesterCard_fontStyle.hash !== "687d8afca93d615ed0794f963702d082" && console.error("The definition of 'FeatureTesterCard_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FeatureTesterCard_fontStyle);
12
13
  export default function FeatureTesterCard(_ref) {
@@ -88,7 +89,7 @@ export default function FeatureTesterCard(_ref) {
88
89
  } = useFeatureTesterAutofit({
89
90
  enabled: autofit,
90
91
  text: content,
91
- fontFamily: `${data.cssFamily} ${data.name}`,
92
+ fontFamily: fontStyleFamily(data.cssFamily, data.name),
92
93
  feature,
93
94
  fontSize: fontSize ?? 72,
94
95
  letterSpacing: letterSpacing ?? 0,
@@ -6,6 +6,7 @@ import { useLazyLoadQuery, graphql } from 'react-relay';
6
6
  import StoreModalCheckoutContext from './StoreModalCheckoutContext.js';
7
7
  import CheckoutSteps from '../Cart/CheckoutSteps.js';
8
8
  import useFontStyle from '../useFontStyle.js';
9
+ import { fontStyleFamily } from '../../fontLoader.js';
9
10
  import StoreModalLoadingScreen from '../elements/StoreModalLoadingScreen/index.js';
10
11
  import ConfigContext from '../ConfigContext.js';
11
12
  const getPageTitle = name => {
@@ -89,8 +90,8 @@ const StoreModalContainer = _ref => {
89
90
  };
90
91
  const [checkoutStep, setCheckoutStep] = useState(CheckoutSteps.Customer);
91
92
  const [checkoutError, setCheckoutError] = useState(null);
92
- const regularName = uiFontStyle ? `${uiFontStyle.cssFamily} ${uiFontStyle.name}` : null;
93
- const boldName = uiFontStyleBold ? `${uiFontStyleBold.cssFamily} ${uiFontStyleBold.name}` : null;
93
+ const regularName = uiFontStyle ? fontStyleFamily(uiFontStyle.cssFamily, uiFontStyle.name) : null;
94
+ const boldName = uiFontStyleBold ? fontStyleFamily(uiFontStyleBold.cssFamily, uiFontStyleBold.name) : null;
94
95
  return /*#__PURE__*/React.createElement(StoreModalCheckoutContext.Provider, {
95
96
  value: {
96
97
  checkoutStep,
@@ -1,7 +1,7 @@
1
1
  import _useFontStyle_fontStyle from "../__generated__/useFontStyle_fontStyle.graphql.js";
2
2
  import { useState, useRef, useEffect } from 'react';
3
3
  import { graphql, useFragment } from 'react-relay';
4
- import { loadFont, isFontLoaded } from '../fontLoader.js';
4
+ import { loadFont, isFontLoaded, fontStyleFamily } from '../fontLoader.js';
5
5
  import { ensureMetricFallback, metricFallbackFamily } from '../metricFallback.js';
6
6
  const FALLBACK_FAMILY = 'Fallback';
7
7
  const useFontStyle = _ref => {
@@ -9,7 +9,7 @@ const useFontStyle = _ref => {
9
9
  fontStyle: fontStyleKey
10
10
  } = _ref;
11
11
  const fontStyle = useFragment((_useFontStyle_fontStyle.hash && _useFontStyle_fontStyle.hash !== "171c546f40909692656c5e143a98d8a3" && console.error("The definition of 'useFontStyle_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _useFontStyle_fontStyle), fontStyleKey ?? null);
12
- const fontFamily = fontStyle ? `${fontStyle.cssFamily} ${fontStyle.name}` : null;
12
+ const fontFamily = fontStyle ? fontStyleFamily(fontStyle.cssFamily, fontStyle.name) : null;
13
13
  const sources = fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.webfontSources;
14
14
  const verticalMetrics = fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.verticalMetrics;
15
15
 
@@ -9,6 +9,30 @@ interface VerticalMetrics {
9
9
  readonly descender: number;
10
10
  readonly lineGap: number | null;
11
11
  }
12
+ /**
13
+ * The family name a font style's FontFace is registered under, and the name
14
+ * CSS must use to render it: `"<cssFamily> <styleName>"`, made safe to parse
15
+ * as an unquoted CSS font-family.
16
+ *
17
+ * Some Safari builds parse the FontFace family argument as CSS, so a name
18
+ * like "Hell's Gate fairypools" (the apostrophe opens an unterminated string)
19
+ * or "0712 Regular" (an identifier can't start with a digit) makes `load()`
20
+ * reject with "SyntaxError: The string did not match the expected pattern."
21
+ * and the style never renders, while Chrome and other WebKit builds take the
22
+ * argument literally. A name that is valid either way works everywhere:
23
+ *
24
+ * - an ASCII character other than a letter, digit, `-` or `_` is escaped as
25
+ * `_<hex code>` (`'` → `_27`), so distinct names stay distinct;
26
+ * - a word starting with a digit, `-<digit>` or `--`, or matching a reserved
27
+ * word, gets a leading `_`.
28
+ *
29
+ * Non-ASCII characters are valid identifier characters and are kept.
30
+ *
31
+ * @example
32
+ * fontStyleFamily("Hell's Gate", 'fairypools') // => 'Hell_27s Gate fairypools'
33
+ * fontStyleFamily('0712', 'Regular') // => '_0712 Regular'
34
+ */
35
+ export declare function fontStyleFamily(cssFamily: string | null | undefined, styleName: string): string;
12
36
  export declare function isFontLoaded(fontFamily: string): boolean;
13
37
  export declare function loadFont(fontFamily: string, sources: ReadonlyArray<FontSource | null>, verticalMetrics?: VerticalMetrics | null): Promise<FontFace | null>;
14
38
  /**
@@ -1,3 +1,38 @@
1
+ // Words CSS refuses as an unquoted font-family identifier (the CSS-wide
2
+ // keywords plus `default`), compared case-insensitively.
3
+ const RESERVED_FAMILY_WORDS = new Set(['inherit', 'initial', 'unset', 'revert', 'revert-layer', 'default']);
4
+
5
+ /**
6
+ * The family name a font style's FontFace is registered under, and the name
7
+ * CSS must use to render it: `"<cssFamily> <styleName>"`, made safe to parse
8
+ * as an unquoted CSS font-family.
9
+ *
10
+ * Some Safari builds parse the FontFace family argument as CSS, so a name
11
+ * like "Hell's Gate fairypools" (the apostrophe opens an unterminated string)
12
+ * or "0712 Regular" (an identifier can't start with a digit) makes `load()`
13
+ * reject with "SyntaxError: The string did not match the expected pattern."
14
+ * and the style never renders, while Chrome and other WebKit builds take the
15
+ * argument literally. A name that is valid either way works everywhere:
16
+ *
17
+ * - an ASCII character other than a letter, digit, `-` or `_` is escaped as
18
+ * `_<hex code>` (`'` → `_27`), so distinct names stay distinct;
19
+ * - a word starting with a digit, `-<digit>` or `--`, or matching a reserved
20
+ * word, gets a leading `_`.
21
+ *
22
+ * Non-ASCII characters are valid identifier characters and are kept.
23
+ *
24
+ * @example
25
+ * fontStyleFamily("Hell's Gate", 'fairypools') // => 'Hell_27s Gate fairypools'
26
+ * fontStyleFamily('0712', 'Regular') // => '_0712 Regular'
27
+ */
28
+ export function fontStyleFamily(cssFamily, styleName) {
29
+ return `${cssFamily ?? ''} ${styleName}`.split(/\s+/).filter(word => word.length > 0).map(cssIdentifierWord).join(' ');
30
+ }
31
+ function cssIdentifierWord(word) {
32
+ const escaped = word.replace(/[\x00-\x7f]/g, char => /[A-Za-z0-9_-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16)}`);
33
+ const unparseable = /^(?:[0-9]|-[0-9]|--|-$)/.test(escaped);
34
+ return unparseable || RESERVED_FAMILY_WORDS.has(escaped.toLowerCase()) ? `_${escaped}` : escaped;
35
+ }
1
36
  const cache = new Map();
2
37
  const loaded = new Set();
3
38
  export function isFontLoaded(fontFamily) {
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.10.0";
11
+ export const version = "3.10.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.10.0",
3
+ "version": "3.10.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",