fontdue-js 3.0.1 → 3.0.3

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,11 @@
1
+ ## 3.0.3
2
+
3
+ - Fixed misaligned style names in `StoreModal`. The feature-glyphs preview is no longer rendered when a style has nothing to show — a variable font with no named instances, or a non-variable style with no representative glyph for its languages — so its empty wrapper no longer indents the style name and rows in a family now line up.
4
+
5
+ ## 3.0.2
6
+
7
+ - Fixed the `CharacterViewer` dropping character groups that have no OpenType feature (such as Uppercase, Lowercase, and Punctuation). The glyph matcher now treats an empty feature list, `null`, and `undefined` as the same “no feature” state, so a featureless group matches whether the API returns `[]` or `null` for it.
8
+
1
9
  ## 3.0.1
2
10
 
3
11
  - Fixed analytics tracking and consent events posting to the wrong address in non-Next.js frameworks. In Vite-based setups (Astro, React Router 7, TanStack Start) that configure the Fontdue URL through `PUBLIC_FONTDUE_URL` / `VITE_FONTDUE_URL` and pass a `preloadedQuery` to `<FontdueProvider>` (rather than an explicit `url` prop), the page-view and consent requests went to a relative `/api/track/page` on the site’s own origin instead of the configured Fontdue server. They now resolve the Fontdue URL the same way GraphQL requests do. Next.js sites were unaffected.
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { compareGlyphs, flattenCharacterList } from "../components/CharacterViewer/glyphMatch.js";
3
+ describe('compareGlyphs', () => {
4
+ it('treats an empty feature array and null as the same "no feature" state', () => {
5
+ // The regression: glyph groups save base sets as `[]`, but the font's
6
+ // glyphNames report base glyphs with `null`. These must match.
7
+ expect(compareGlyphs({
8
+ string: 'a',
9
+ features: []
10
+ }, {
11
+ string: 'a',
12
+ features: null
13
+ })).toBe(true);
14
+ expect(compareGlyphs({
15
+ string: 'a',
16
+ features: null
17
+ }, {
18
+ string: 'a',
19
+ features: []
20
+ })).toBe(true);
21
+ expect(compareGlyphs({
22
+ string: 'a',
23
+ features: undefined
24
+ }, {
25
+ string: 'a',
26
+ features: []
27
+ })).toBe(true);
28
+ });
29
+ it('matches identical single features', () => {
30
+ expect(compareGlyphs({
31
+ string: 'a',
32
+ features: ['ss01']
33
+ }, {
34
+ string: 'a',
35
+ features: ['ss01']
36
+ })).toBe(true);
37
+ });
38
+ it('does not match a feature-bearing set against a base glyph', () => {
39
+ expect(compareGlyphs({
40
+ string: 'a',
41
+ features: ['ss01']
42
+ }, {
43
+ string: 'a',
44
+ features: null
45
+ })).toBe(false);
46
+ expect(compareGlyphs({
47
+ string: 'a',
48
+ features: ['ss01']
49
+ }, {
50
+ string: 'a',
51
+ features: []
52
+ })).toBe(false);
53
+ });
54
+ it('does not match different features or different strings', () => {
55
+ expect(compareGlyphs({
56
+ string: 'a',
57
+ features: ['ss01']
58
+ }, {
59
+ string: 'a',
60
+ features: ['ss02']
61
+ })).toBe(false);
62
+ expect(compareGlyphs({
63
+ string: 'a',
64
+ features: []
65
+ }, {
66
+ string: 'b',
67
+ features: []
68
+ })).toBe(false);
69
+ });
70
+ });
71
+ describe('flattenCharacterList', () => {
72
+ // glyphNames as the backend reports them: base glyphs carry `features: null`.
73
+ const glyphNames = [{
74
+ characters: 'a',
75
+ features: null
76
+ }, {
77
+ characters: 'b',
78
+ features: null
79
+ }, {
80
+ characters: 'c',
81
+ features: null
82
+ }, {
83
+ characters: 'a',
84
+ features: ['ss01']
85
+ }];
86
+ it('keeps a no-feature range whose glyphs exist as base glyphs (the storefront regression)', () => {
87
+ const result = flattenCharacterList({
88
+ characters: [{
89
+ __typename: 'CharacterRange',
90
+ first: 'a',
91
+ last: 'c'
92
+ }],
93
+ features: [] // saved by the editor as an empty array
94
+ }, glyphNames);
95
+ // Previously this returned [] (every char dropped) and the group vanished.
96
+ expect(result === null || result === void 0 ? void 0 : result.characters).toEqual(['a', 'b', 'c']);
97
+ });
98
+ it('drops characters the font does not contain', () => {
99
+ const result = flattenCharacterList({
100
+ characters: [{
101
+ __typename: 'CharacterRange',
102
+ first: 'a',
103
+ last: 'd'
104
+ }],
105
+ features: []
106
+ }, glyphNames);
107
+ expect(result === null || result === void 0 ? void 0 : result.characters).toEqual(['a', 'b', 'c']); // 'd' is not in the font
108
+ });
109
+ it('matches feature sets only against glyphs reachable via that feature', () => {
110
+ const result = flattenCharacterList({
111
+ characters: [{
112
+ __typename: 'CharacterString',
113
+ string: 'a'
114
+ }, {
115
+ __typename: 'CharacterString',
116
+ string: 'b'
117
+ }],
118
+ features: ['ss01']
119
+ }, glyphNames);
120
+ // Only 'a' has an ss01 form; 'b' does not.
121
+ expect(result === null || result === void 0 ? void 0 : result.characters).toEqual(['a']);
122
+ });
123
+ });
@@ -24,7 +24,7 @@ const openCart = () => ({
24
24
  function CartButtonComponent(_ref) {
25
25
  let {
26
26
  label,
27
- buttonStyle,
27
+ buttonStyle = 'inline',
28
28
  order: orderKey,
29
29
  children,
30
30
  suffix
@@ -0,0 +1,29 @@
1
+ export interface Glyph {
2
+ string: string;
3
+ features?: readonly string[] | null;
4
+ }
5
+ export type CharacterRange = {
6
+ __typename: 'CharacterRange';
7
+ first: string;
8
+ last: string;
9
+ };
10
+ export type CharacterString = {
11
+ __typename: 'CharacterString';
12
+ string: string;
13
+ };
14
+ export type CharacterItem = CharacterRange | CharacterString | {
15
+ __typename: '%other';
16
+ };
17
+ export declare function charCode(c: string): number;
18
+ export declare function fromCharCode(code: number): string;
19
+ export declare function compareGlyphs(a: Glyph | undefined, b: Glyph | undefined): boolean;
20
+ export declare function flattenCharacterList(charSet: {
21
+ characters: readonly CharacterItem[] | null;
22
+ features: readonly string[] | null;
23
+ } | null, glyphNames: readonly {
24
+ characters: string;
25
+ features: readonly string[] | null;
26
+ }[] | null): {
27
+ characters: string[];
28
+ features?: readonly string[] | null;
29
+ } | null;
@@ -0,0 +1,56 @@
1
+ // Glyph matching for the character viewer.
2
+ //
3
+ // A glyph group's character set is matched against the font's `glyphNames` so
4
+ // only characters the font actually contains are rendered. The subtle part is
5
+ // the OpenType `features` array: a base glyph ("no feature") is expressed as
6
+ // `null` in `glyphNames` (and in the backend's auto-generated default groups),
7
+ // but the glyph-group editor saves base sets with an empty array `[]`. These
8
+ // must be treated as the same "no features" state — otherwise every no-feature
9
+ // group (Uppercase, Lowercase, Punctuation, …) renders empty and is dropped.
10
+
11
+ export function charCode(c) {
12
+ return c.codePointAt(0) ?? 0;
13
+ }
14
+ export function fromCharCode(code) {
15
+ return String.fromCodePoint(code);
16
+ }
17
+ export function compareGlyphs(a, b) {
18
+ if (!a || !b) return false;
19
+ if (a.string !== b.string) return false;
20
+ // Normalize missing/empty features to a single representation. Comparing the
21
+ // raw arrays' lengths treated `[]` (length 0) and `null`/`undefined` (no
22
+ // length) as different, so a base group saved as `[]` never matched a base
23
+ // glyph reported as `null` and the group disappeared on the storefront.
24
+ const fa = a.features ?? [];
25
+ const fb = b.features ?? [];
26
+ if (fa.length !== fb.length) return false;
27
+ return fa.every(feature => fb.includes(feature));
28
+ }
29
+ export function flattenCharacterList(charSet, glyphNames) {
30
+ if (!glyphNames || !charSet || !charSet.characters) return null;
31
+ return {
32
+ features: charSet.features,
33
+ characters: charSet.characters.reduce((acc, item) => {
34
+ if (item.__typename === 'CharacterString') {
35
+ acc.push(item.string);
36
+ } else if (item.__typename === 'CharacterRange') {
37
+ for (let code = charCode(item.first); code <= charCode(item.last); code++) {
38
+ acc.push(fromCharCode(code));
39
+ }
40
+ }
41
+ return acc;
42
+ }, []).filter(string => glyphNames.some(_ref => {
43
+ let {
44
+ characters,
45
+ features
46
+ } = _ref;
47
+ return compareGlyphs({
48
+ string,
49
+ features: charSet.features
50
+ }, {
51
+ string: characters,
52
+ features
53
+ });
54
+ }))
55
+ };
56
+ }
@@ -22,6 +22,7 @@ import CharacterViewerStyleRefetchQueryNode from '../../__generated__/CharacterV
22
22
  import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
23
23
  import ConfigContext from '../ConfigContext.js';
24
24
  import { unicodeNamesUrl } from '../../data/unicodeNamesUrl.js';
25
+ import { compareGlyphs, flattenCharacterList } from './glyphMatch.js';
25
26
  function useSize() {
26
27
  const ref = useRef(null);
27
28
  const [size, setSize] = React.useState();
@@ -99,53 +100,6 @@ function useUnicodeData() {
99
100
  }, [url]);
100
101
  return data;
101
102
  }
102
- function compareGlyphs(a, b) {
103
- var _a$features, _b$features;
104
- if (!a || !b) return false;
105
- if (a.string !== b.string) return false;
106
- if (((_a$features = a.features) === null || _a$features === void 0 ? void 0 : _a$features.length) !== ((_b$features = b.features) === null || _b$features === void 0 ? void 0 : _b$features.length)) return false;
107
- if (a.features && b.features) {
108
- return a.features.every(feature => {
109
- var _b$features2;
110
- return (_b$features2 = b.features) === null || _b$features2 === void 0 ? void 0 : _b$features2.includes(feature);
111
- });
112
- }
113
- return true;
114
- }
115
- function charCode(c) {
116
- return c.codePointAt(0) ?? 0;
117
- }
118
- function fromCharCode(code) {
119
- return String.fromCodePoint(code);
120
- }
121
- function flattenCharacterList(charSet, glyphNames) {
122
- if (!glyphNames || !charSet || !charSet.characters) return null;
123
- return {
124
- features: charSet.features,
125
- characters: charSet.characters.reduce((acc, item) => {
126
- if (item.__typename === 'CharacterString') {
127
- acc.push(item.string);
128
- } else if (item.__typename === 'CharacterRange') {
129
- for (let code = charCode(item.first); code <= charCode(item.last); code++) {
130
- acc.push(fromCharCode(code));
131
- }
132
- }
133
- return acc;
134
- }, []).filter(string => glyphNames.some(_ref2 => {
135
- let {
136
- characters,
137
- features
138
- } = _ref2;
139
- return compareGlyphs({
140
- string,
141
- features: charSet.features
142
- }, {
143
- string: characters,
144
- features
145
- });
146
- }))
147
- };
148
- }
149
103
  _CharacterViewer_family.hash && _CharacterViewer_family.hash !== "162b1a99fe187b20ee8560018cd5b3ce" && console.error("The definition of 'CharacterViewer_family' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CharacterViewer_family;
150
104
  function areCombiningChars(input) {
151
105
  for (const char of input) {
@@ -170,10 +124,10 @@ function hasUnsupportedPositionalFeatures(features) {
170
124
  if (!features) return false;
171
125
  return features.some(f => UNSUPPORTED_POSITIONAL_FEATURES.includes(f));
172
126
  }
173
- function CharacterViewerComponent(_ref3) {
127
+ function CharacterViewerComponent(_ref2) {
174
128
  let {
175
129
  collection: collectionKey
176
- } = _ref3;
130
+ } = _ref2;
177
131
  const collection = useFragment((_CharacterViewer_collection.hash && _CharacterViewer_collection.hash !== "c2116c3c2c795e5306c3a2a0ecd60137" && console.error("The definition of 'CharacterViewer_collection' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CharacterViewer_collection), collectionKey);
178
132
  const [fontStyle, refetchFontStyle] = useRefetchableFragment((_CharacterViewer_style.hash && _CharacterViewer_style.hash !== "8cbf3328030930b88b3ec3601aaf7c45" && console.error("The definition of 'CharacterViewer_style' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CharacterViewer_style), collection.featureStyle);
179
133
  let families = [];
@@ -221,11 +175,11 @@ function CharacterViewerComponent(_ref3) {
221
175
  const visibleGlyph = activeGlyph ?? selectedGlyph;
222
176
  const getGlyphName = useCallback(glyph => {
223
177
  var _fontStyle$glyphNames, _fontStyle$glyphNames2;
224
- return (fontStyle === null || fontStyle === void 0 ? void 0 : (_fontStyle$glyphNames = fontStyle.glyphNames) === null || _fontStyle$glyphNames === void 0 ? void 0 : (_fontStyle$glyphNames2 = _fontStyle$glyphNames.find(_ref4 => {
178
+ return (fontStyle === null || fontStyle === void 0 ? void 0 : (_fontStyle$glyphNames = fontStyle.glyphNames) === null || _fontStyle$glyphNames === void 0 ? void 0 : (_fontStyle$glyphNames2 = _fontStyle$glyphNames.find(_ref3 => {
225
179
  let {
226
180
  characters,
227
181
  features
228
- } = _ref4;
182
+ } = _ref3;
229
183
  return compareGlyphs(glyph, {
230
184
  string: characters,
231
185
  features
@@ -310,12 +264,12 @@ function CharacterViewerComponent(_ref3) {
310
264
  threshold > 0 && visible.some(v => Math.abs(v.position - line.position) < threshold) ? visible : [...visible, line], []);
311
265
  const groups = useMemo(() => {
312
266
  var _collection$glyphGrou;
313
- return (_collection$glyphGrou = collection.glyphGroups) === null || _collection$glyphGrou === void 0 ? void 0 : _collection$glyphGrou.map(_ref5 => {
267
+ return (_collection$glyphGrou = collection.glyphGroups) === null || _collection$glyphGrou === void 0 ? void 0 : _collection$glyphGrou.map(_ref4 => {
314
268
  var _characterSets$filter;
315
269
  let {
316
270
  name,
317
271
  characterSets
318
- } = _ref5;
272
+ } = _ref4;
319
273
  return {
320
274
  name,
321
275
  characterSets: characterSets
@@ -388,11 +342,11 @@ function CharacterViewerComponent(_ref3) {
388
342
  style: cellWidth ? {
389
343
  '--cell-width': `${cellWidth}px`
390
344
  } : undefined
391
- }, groups === null || groups === void 0 ? void 0 : groups.map((_ref6, i) => {
345
+ }, groups === null || groups === void 0 ? void 0 : groups.map((_ref5, i) => {
392
346
  let {
393
347
  name,
394
348
  characterSets
395
- } = _ref6;
349
+ } = _ref5;
396
350
  return /*#__PURE__*/React.createElement("div", {
397
351
  className: "character-viewer__block",
398
352
  key: i,
@@ -438,10 +392,10 @@ export async function loadCharacterViewerQuery(variables, options) {
438
392
  }
439
393
  throw new Error('loadCharacterViewerQuery expected either collectionId or collectionSlug');
440
394
  }
441
- function CharacterViewerIdQueryRenderer(_ref7) {
395
+ function CharacterViewerIdQueryRenderer(_ref6) {
442
396
  let {
443
397
  collectionId
444
- } = _ref7;
398
+ } = _ref6;
445
399
  const data = useLazyLoadQuery(idQuery, {
446
400
  collectionId
447
401
  });
@@ -450,11 +404,11 @@ function CharacterViewerIdQueryRenderer(_ref7) {
450
404
  collection: data.node
451
405
  });
452
406
  }
453
- export function CharacterViewerPreloadedIDQueryRenderer(_ref8) {
407
+ export function CharacterViewerPreloadedIDQueryRenderer(_ref7) {
454
408
  let {
455
409
  preloadedQuery,
456
410
  ...rest
457
- } = _ref8;
411
+ } = _ref7;
458
412
  // The query node lets the hook commit the payload into the store, so
459
413
  // usePreloadedQuery resolves synchronously during SSR instead of
460
414
  // refetching (the response cache only exists in the browser).
@@ -465,11 +419,11 @@ export function CharacterViewerPreloadedIDQueryRenderer(_ref8) {
465
419
  collection: data.node
466
420
  }));
467
421
  }
468
- function CharacterViewerSlugQueryRenderer(_ref9) {
422
+ function CharacterViewerSlugQueryRenderer(_ref8) {
469
423
  var _data$viewer, _data$viewer$slug;
470
424
  let {
471
425
  collectionSlug
472
- } = _ref9;
426
+ } = _ref8;
473
427
  const data = useLazyLoadQuery(slugQuery, {
474
428
  collectionSlug
475
429
  });
@@ -479,12 +433,12 @@ function CharacterViewerSlugQueryRenderer(_ref9) {
479
433
  collection: collection
480
434
  });
481
435
  }
482
- export function CharacterViewerPreloadedSlugQueryRenderer(_ref10) {
436
+ export function CharacterViewerPreloadedSlugQueryRenderer(_ref9) {
483
437
  var _data$viewer2, _data$viewer2$slug;
484
438
  let {
485
439
  preloadedQuery,
486
440
  ...rest
487
- } = _ref10;
441
+ } = _ref9;
488
442
  // The query node lets the hook commit the payload into the store, so
489
443
  // usePreloadedQuery resolves synchronously during SSR instead of
490
444
  // refetching (the response cache only exists in the browser).
@@ -496,11 +450,11 @@ export function CharacterViewerPreloadedSlugQueryRenderer(_ref10) {
496
450
  collection: collection
497
451
  }));
498
452
  }
499
- function CharacterViewerPreloadedRenderer(_ref11) {
453
+ function CharacterViewerPreloadedRenderer(_ref10) {
500
454
  let {
501
455
  preloadedQuery,
502
456
  ...rest
503
- } = _ref11;
457
+ } = _ref10;
504
458
  if (preloadedQuery.params.name === 'CharacterViewerIDQuery') {
505
459
  return /*#__PURE__*/React.createElement(CharacterViewerPreloadedIDQueryRenderer, _extends({
506
460
  preloadedQuery: preloadedQuery
@@ -11,7 +11,7 @@ import StoreModalStyleButtonElement from '../elements/StoreModalStyleButton/inde
11
11
  import { getFeatureGlyphs, variableInstanceCSS } from '../../utils.js';
12
12
  const THIN_SPACE = '\u2009';
13
13
  export default function StoreModalStyleButton(_ref) {
14
- var _fontStyle$variableIn;
14
+ var _fontStyle$variableIn, _fontStyle$variableIn2;
15
15
  let {
16
16
  fontStyle: fontStyleKey,
17
17
  onSelectSku,
@@ -24,17 +24,18 @@ export default function StoreModalStyleButton(_ref) {
24
24
  if (!fontStyle.sku) return;
25
25
  onSelectSku(fontStyle.sku.id, !selected);
26
26
  };
27
+ const hasFeatureGlyphs = isVariableFont ? Boolean((_fontStyle$variableIn = fontStyle.variableInstances) === null || _fontStyle$variableIn === void 0 ? void 0 : _fontStyle$variableIn.length) : getFeatureGlyphs(fontStyle.supportedLanguages) != null;
27
28
  return /*#__PURE__*/React.createElement(StoreModalStyleButtonElement, _extends({}, rest, {
28
29
  onClick: fontStyle.sku ? handleSelect : undefined,
29
30
  selected: selected
30
31
  }), {
31
- featureGlyphs: /*#__PURE__*/React.createElement(FontStyle, {
32
+ featureGlyphs: hasFeatureGlyphs ? /*#__PURE__*/React.createElement(FontStyle, {
32
33
  fontStyle: fontStyle,
33
34
  Component: "span"
34
- }, isVariableFont ? (_fontStyle$variableIn = fontStyle.variableInstances) === null || _fontStyle$variableIn === void 0 ? void 0 : _fontStyle$variableIn.map((instance, i) => /*#__PURE__*/React.createElement("span", {
35
+ }, isVariableFont ? (_fontStyle$variableIn2 = fontStyle.variableInstances) === null || _fontStyle$variableIn2 === void 0 ? void 0 : _fontStyle$variableIn2.map((instance, i) => /*#__PURE__*/React.createElement("span", {
35
36
  key: instance.name,
36
37
  style: variableInstanceCSS(instance)
37
- }, i !== 0 && THIN_SPACE, getFeatureGlyphs(fontStyle.supportedLanguages))) : getFeatureGlyphs(fontStyle.supportedLanguages)),
38
+ }, i !== 0 && THIN_SPACE, getFeatureGlyphs(fontStyle.supportedLanguages))) : getFeatureGlyphs(fontStyle.supportedLanguages)) : null,
38
39
  // for variable fonts, only show the name of the style if there are
39
40
  // multiple styles
40
41
  name: isVariableFont ? fontStyle.sku ? fontStyle.name : undefined : fontStyle.name,
@@ -17,9 +17,9 @@ const StoreModalStyleButton = _ref => {
17
17
  "data-clickable": Boolean(rest.onClick)
18
18
  }), /*#__PURE__*/React.createElement("div", {
19
19
  className: "store-modal__style-button__container"
20
- }, /*#__PURE__*/React.createElement("span", {
20
+ }, featureGlyphs && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
21
21
  className: "store-modal__style-button__feature-glyphs"
22
- }, featureGlyphs), ' ', /*#__PURE__*/React.createElement("span", {
22
+ }, featureGlyphs), ' '), /*#__PURE__*/React.createElement("span", {
23
23
  className: "store-modal__style-button__style-name"
24
24
  }, name), selected ? /*#__PURE__*/React.createElement("div", {
25
25
  className: "store-modal__style-button__checked"
package/dist/corsError.js CHANGED
@@ -51,7 +51,7 @@ async function isCorsBlocked(fetchUrl) {
51
51
  }
52
52
  }
53
53
  function publishConnectionError(origin, fetchUrl) {
54
- console.error(`[Fontdue] Cross-origin request to ${fetchUrl} was blocked.\n\n` + `Your website (${origin}) is most likely not listed as an allowed ` + `origin in your Fontdue settings.\n\n` + `To fix this:\n` + `1. Log in to your Fontdue dashboard\n` + `2. Go to Settings Integration\n` + `3. Add "${origin}" to your allowed origins\n` + `4. Save this page will reload automatically\n\n` + `If "${origin}" is already listed, the connection may be failing for ` + `another reason (a network problem or a temporary Fontdue outage).`);
54
+ console.error(`[Fontdue] Cross-origin request to ${fetchUrl} was blocked.\n\n` + `Your website (${origin}) is most likely not listed as an allowed ` + `origin in your Fontdue settings.\n\n` + `To fix this:\n` + `1. Log in to your Fontdue dashboard\n` + `2. Go to Settings \u2192 Integration\n` + `3. Add "${origin}" to the "Cross-origin API access" field\n` + `4. Save \u2014 this page will reload automatically\n\n` + `If "${origin}" is already listed, the connection may be failing for ` + `another reason (a network problem or a temporary Fontdue outage).`);
55
55
 
56
56
  // Surface the connection status as a compact, toolbar-styled widget (red
57
57
  // button + alert glyph) rather than the old full-screen modal. It mounts in
package/dist/fontdue.css CHANGED
@@ -397,11 +397,20 @@ div[data-component=TypeTesters] {
397
397
  width: 100%;
398
398
  }
399
399
 
400
+ fontdue-type-testers,
401
+ fontdue-type-tester {
402
+ display: block;
403
+ max-width: 100%;
404
+ min-width: 0;
405
+ }
406
+
400
407
  .type-tester {
401
408
  margin-bottom: 20px;
402
409
  font-size: 1rem;
403
410
  border-top: 1px solid var(--horizontal_rule_color);
404
411
  color: var(--primary_text_color);
412
+ min-width: 0;
413
+ max-width: 100%;
405
414
  }
406
415
  @media (max-width: 899px) {
407
416
  .type-tester {
@@ -586,7 +595,7 @@ div[data-component=TypeTesters] {
586
595
  cursor: pointer;
587
596
  }
588
597
  .type-tester__slider__handle {
589
- background-color: var(--primary_background_color);
598
+ background-color: var(--primary_background_color, canvas);
590
599
  border: 1px solid currentcolor;
591
600
  border-radius: 50%;
592
601
  height: 15px;
@@ -1728,6 +1737,7 @@ input:checked + .checkbox__icon {
1728
1737
  padding-left: 0;
1729
1738
  padding-right: 0;
1730
1739
  box-shadow: none;
1740
+ background: transparent;
1731
1741
  }
1732
1742
 
1733
1743
  .store-modal__family-button__container {
@@ -7,7 +7,7 @@ import { PREVIEW_HEADER, hasPreviewMarkerCookie } from '../preview/constants.js'
7
7
  // (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
8
8
  // Exported so UI (the admin toolbar) can surface it without re-reading the
9
9
  // build-time global in a 'use client' module.
10
- export const version = "3.0.1";
10
+ export const version = "3.0.3";
11
11
  const IS_SERVER = typeof window === typeof undefined;
12
12
 
13
13
  // Opt server fetches into Next's data cache only in production; dev stays
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fontdue-js",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run relay && run-p build-js build-css build-ts",
@@ -123,9 +123,9 @@
123
123
  "react-server": "./dist/components/TypeTesters/index.server.js",
124
124
  "default": "./dist/components/TypeTesters/index.js"
125
125
  },
126
+ "./useAutofit": "./dist/hooks/useAutofit.js",
126
127
  "./useConsent": "./dist/components/useConsent.js",
127
128
  "./useFont": "./dist/components/useFont.js",
128
- "./useFontStyle": "./dist/components/useFont.js",
129
- "./useAutofit": "./dist/hooks/useAutofit.js"
129
+ "./useFontStyle": "./dist/components/useFont.js"
130
130
  }
131
131
  }