fontdue-js 2.26.0 → 2.28.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 (87) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/__generated__/TypeTesterFamiliesQuery.graphql.d.ts +29 -0
  3. package/dist/__generated__/TypeTesterFamiliesQuery.graphql.js +174 -0
  4. package/dist/__generated__/TypeTesterFamiliesStylesQuery.graphql.d.ts +35 -0
  5. package/dist/__generated__/TypeTesterFamiliesStylesQuery.graphql.js +170 -0
  6. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
  7. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +4 -4
  8. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -3
  9. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +78 -161
  10. package/dist/__generated__/TypeTesterStyleSelectData_fontStyle.graphql.d.ts +2 -1
  11. package/dist/__generated__/TypeTesterStyleSelectData_fontStyle.graphql.js +18 -17
  12. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
  13. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +4 -4
  14. package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -5
  15. package/dist/__generated__/TypeTestersIDQuery.graphql.js +101 -195
  16. package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
  17. package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +4 -4
  18. package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -3
  19. package/dist/__generated__/TypeTestersSlugQuery.graphql.js +102 -178
  20. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.d.ts +27 -0
  21. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +72 -0
  22. package/dist/components/Cart/CartOrder.js +8 -0
  23. package/dist/components/Cart/orderTracking.d.ts +10 -0
  24. package/dist/components/Cart/orderTracking.js +43 -0
  25. package/dist/components/ConfigContext.d.ts +2 -2
  26. package/dist/components/FontStyle/index.d.ts +2 -0
  27. package/dist/components/FontStyle/index.js +4 -2
  28. package/dist/components/FontdueProvider/FontdueProviderClientComponent.js +2 -1
  29. package/dist/components/TypeTester/TypeTesterFamilies.d.ts +37 -0
  30. package/dist/components/TypeTester/TypeTesterFamilies.js +128 -0
  31. package/dist/components/TypeTester/TypeTesterStandalone.js +2 -7
  32. package/dist/components/TypeTester/TypeTesterStandalone.server.js +1 -2
  33. package/dist/components/TypeTester/TypeTesterStyleSelect.d.ts +15 -5
  34. package/dist/components/TypeTester/TypeTesterStyleSelect.js +57 -47
  35. package/dist/components/TypeTester/TypeTesterStyleSelectData.d.ts +1 -3
  36. package/dist/components/TypeTester/TypeTesterStyleSelectData.js +210 -52
  37. package/dist/components/TypeTester/TypeTesterVariableAxes.js +17 -4
  38. package/dist/components/TypeTester/index.d.ts +1 -3
  39. package/dist/components/TypeTester/index.js +13 -6
  40. package/dist/components/TypeTester/useTypeTesterStyler.d.ts +4 -2
  41. package/dist/components/TypeTester/useTypeTesterStyler.js +78 -11
  42. package/dist/components/TypeTesters/index.js +6 -20
  43. package/dist/components/TypeTesters/index.server.js +2 -4
  44. package/dist/components/elements/StoreModalUnifiedCheckout.js +8 -0
  45. package/dist/components/useFontStyle.d.ts +7 -0
  46. package/dist/components/useFontStyle.js +2 -1
  47. package/dist/fontdue.css +9 -16
  48. package/dist/hooks.d.ts +1 -0
  49. package/dist/hooks.js +27 -0
  50. package/fontdue.css +2 -1
  51. package/package.json +1 -1
  52. package/dist/__generated__/FontdueProviderQuery.graphql.d.ts +0 -19
  53. package/dist/__generated__/FontdueProviderQuery.graphql.js +0 -140
  54. package/dist/__generated__/ServerConfigProvider_viewer.graphql.d.ts +0 -23
  55. package/dist/__generated__/ServerConfigProvider_viewer.graphql.js +0 -57
  56. package/dist/__generated__/TestModeBanner_viewer.graphql.d.ts +0 -19
  57. package/dist/__generated__/TestModeBanner_viewer.graphql.js +0 -36
  58. package/dist/__generated__/ThemeConfig_viewer.graphql.d.ts +0 -19
  59. package/dist/__generated__/ThemeConfig_viewer.graphql.js +0 -36
  60. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.d.ts +0 -42
  61. package/dist/__generated__/TypeTesterStyleSelectData_viewer.graphql.js +0 -166
  62. package/dist/__generated__/TypeTester_viewer.graphql.d.ts +0 -17
  63. package/dist/__generated__/TypeTester_viewer.graphql.js +0 -40
  64. package/dist/__generated__/TypeTesters_viewer.graphql.d.ts +0 -17
  65. package/dist/__generated__/TypeTesters_viewer.graphql.js +0 -40
  66. package/dist/components/BuyingOptions/index.d.ts +0 -9
  67. package/dist/components/CookieNotification/index.d.ts +0 -13
  68. package/dist/components/FontdueContextProvider/index.d.ts +0 -17
  69. package/dist/components/FontdueContextProvider/index.js +0 -108
  70. package/dist/components/FontdueContextProvider/index.server.d.ts +0 -4
  71. package/dist/components/FontdueContextProvider/index.server.js +0 -7
  72. package/dist/components/FontdueProvider/useAuxUIOwner.d.ts +0 -1
  73. package/dist/components/FontdueProvider/useAuxUIOwner.js +0 -28
  74. package/dist/components/TypeTester/TypeTesterStandalone.preload.d.ts +0 -14
  75. package/dist/components/TypeTester/TypeTesterStandalone.preload.js +0 -20
  76. package/dist/config.d.ts +0 -7
  77. package/dist/config.js +0 -31
  78. package/dist/global-shim.d.ts +0 -1
  79. package/dist/global-shim.js +0 -8
  80. package/dist/hooks/useResizeObserver.d.ts +0 -11
  81. package/dist/hooks/useResizeObserver.js +0 -23
  82. package/dist/index.d.ts +0 -2
  83. package/dist/index.js +0 -1
  84. package/dist/loadFontdueProviderQuery.d.ts +0 -3
  85. package/dist/loadFontdueProviderQuery.js +0 -10
  86. package/dist/vite.d.ts +0 -2
  87. package/dist/vite.js +0 -139
@@ -4,19 +4,18 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = TypeTesterStyleSelectData;
7
- var _TypeTesterStyleSelectData_viewer2 = _interopRequireDefault(require("../../__generated__/TypeTesterStyleSelectData_viewer.graphql"));
8
7
  var _TypeTesterStyleSelectData_fontStyle2 = _interopRequireDefault(require("../../__generated__/TypeTesterStyleSelectData_fontStyle.graphql"));
9
8
  var _TypeTesterStyleSelectData_fontStyleData2 = _interopRequireDefault(require("../../__generated__/TypeTesterStyleSelectData_fontStyleData.graphql"));
10
9
  var _react = _interopRequireWildcard(require("react"));
11
10
  var _reactRelay = require("react-relay");
11
+ var _relayRuntime = require("relay-runtime");
12
12
  var _utils = require("../../utils");
13
13
  var _TypeTesterStyleSelect = _interopRequireDefault(require("./TypeTesterStyleSelect"));
14
- var _relayRuntime = require("relay-runtime");
14
+ var _TypeTesterFamilies = require("./TypeTesterFamilies");
15
15
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
16
16
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
17
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
18
  _TypeTesterStyleSelectData_fontStyleData2.default.hash && _TypeTesterStyleSelectData_fontStyleData2.default.hash !== "fb09207ccab08c7e73677486469a3c95" && console.error("The definition of 'TypeTesterStyleSelectData_fontStyleData' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStyleSelectData_fontStyleData2.default;
19
-
20
19
  // get a score for similarity between two styles
21
20
  // 0 < n <= 1
22
21
  // where 1 = exact match
@@ -37,77 +36,236 @@ const computeSimilarity = (left, right) => {
37
36
  const stretchSimilarity = left.cssStretch === right.cssStretch ? 1 : 0.9;
38
37
  return weightSimilarity * styleSimilarity * stretchSimilarity;
39
38
  };
40
- function TypeTesterStyleSelectData(_ref) {
41
- var _viewer$families, _viewer$families$edge, _fontStyle$variableIn;
39
+
40
+ // Build the `selectedFamily` shape the dropdown expects. Uses the lazily
41
+ // fetched styles when available, otherwise stubs the current family with just
42
+ // the current style so the collapsed selector still shows something meaningful.
43
+ function buildSelectedFamily(selectedFamilyId, entry, fontStyle, families) {
44
+ var _fontStyle$family, _families$find, _fontStyle$family2;
45
+ const currentFamilyId = ((_fontStyle$family = fontStyle.family) === null || _fontStyle$family === void 0 ? void 0 : _fontStyle$family.id) ?? '';
46
+ const name = (families === null || families === void 0 ? void 0 : (_families$find = families.find(f => f.id === selectedFamilyId)) === null || _families$find === void 0 ? void 0 : _families$find.name) ?? (selectedFamilyId === currentFamilyId ? (_fontStyle$family2 = fontStyle.family) === null || _fontStyle$family2 === void 0 ? void 0 : _fontStyle$family2.name : undefined) ?? '';
47
+ if (entry !== null && entry !== void 0 && entry.styles) {
48
+ return {
49
+ id: selectedFamilyId,
50
+ name,
51
+ isVariableFont: entry.isVariableFont,
52
+ fontStyles: entry.styles.map(style => ({
53
+ id: style.id,
54
+ name: style.name,
55
+ variableInstances: style.variableInstances
56
+ }))
57
+ };
58
+ }
59
+ if (selectedFamilyId === currentFamilyId) {
60
+ var _fontStyle$variableIn;
61
+ return {
62
+ id: selectedFamilyId,
63
+ name,
64
+ isVariableFont: (((_fontStyle$variableIn = fontStyle.variableInstances) === null || _fontStyle$variableIn === void 0 ? void 0 : _fontStyle$variableIn.length) ?? 0) > 0,
65
+ fontStyles: [{
66
+ id: fontStyle.id,
67
+ name: fontStyle.name,
68
+ variableInstances: fontStyle.variableInstances ?? null
69
+ }]
70
+ };
71
+ }
72
+
73
+ // Switched to a family whose styles haven't arrived yet.
74
+ return {
75
+ id: selectedFamilyId,
76
+ name,
77
+ isVariableFont: false,
78
+ fontStyles: null
79
+ };
80
+ }
81
+ function TypeTesterStyleSelectSelectable(_ref) {
82
+ var _fontStyle$family3;
42
83
  let {
43
- fontStyle: fontStyleKey,
44
- viewer: viewerKey,
84
+ fontStyle,
85
+ variableSettings,
45
86
  onSelectFontStyleValue,
46
87
  onSelectVariableAxes,
47
- config,
48
- variableSettings,
49
- includeVariableAxesOption
88
+ includeVariableAxesOption,
89
+ fallbackName
50
90
  } = _ref;
51
- const fontStyle = (0, _reactRelay.useFragment)((_TypeTesterStyleSelectData_fontStyle2.default.hash && _TypeTesterStyleSelectData_fontStyle2.default.hash !== "187778816a910be9ab709431d1d818b3" && console.error("The definition of 'TypeTesterStyleSelectData_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStyleSelectData_fontStyle2.default), fontStyleKey);
52
- const viewer = (0, _reactRelay.useFragment)((_TypeTesterStyleSelectData_viewer2.default.hash && _TypeTesterStyleSelectData_viewer2.default.hash !== "5e5834d9202f90bdde5a0a7db49e8639" && console.error("The definition of 'TypeTesterStyleSelectData_viewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStyleSelectData_viewer2.default), viewerKey);
53
- const familyData = viewer === null || viewer === void 0 ? void 0 : (_viewer$families = viewer.families) === null || _viewer$families === void 0 ? void 0 : (_viewer$families$edge = _viewer$families.edges) === null || _viewer$families$edge === void 0 ? void 0 : _viewer$families$edge.map(edge => edge.node).filter(_utils.notEmpty).filter(family => {
54
- var _family$featureStyle, _family$featureStyle$;
55
- return (// make sure the family options' supportedLanguages intersect with the current selection's supportedLanuages.
56
- // e.g. if the current font style only supports 'ar', only show families that include 'ar'
57
- (_family$featureStyle = family.featureStyle) === null || _family$featureStyle === void 0 ? void 0 : (_family$featureStyle$ = _family$featureStyle.supportedLanguages) === null || _family$featureStyle$ === void 0 ? void 0 : _family$featureStyle$.some(lang => {
58
- var _fontStyle$supportedL;
59
- return (_fontStyle$supportedL = fontStyle.supportedLanguages) === null || _fontStyle$supportedL === void 0 ? void 0 : _fontStyle$supportedL.includes(lang);
60
- })
61
- );
62
- });
91
+ const {
92
+ families,
93
+ loadingFamilies,
94
+ loadFamilies,
95
+ getFamilyStyles,
96
+ loadFamilyStyles
97
+ } = (0, _TypeTesterFamilies.useTypeTesterFamilies)();
98
+ const currentFamilyId = ((_fontStyle$family3 = fontStyle.family) === null || _fontStyle$family3 === void 0 ? void 0 : _fontStyle$family3.id) ?? '';
99
+
100
+ // The family the user has switched to but whose styles are still loading.
101
+ // We keep the style selector + font on the *current* family until this
102
+ // family's data is ready, then commit family + style + font together — so the
103
+ // toolbar never flashes a blank style selector mid-switch.
104
+ const [pendingFamilyId, setPendingFamilyId] = (0, _react.useState)(null);
105
+
106
+ // Tracks the family we've already submitted a style pick for, so the pick
107
+ // effect fires exactly once per switch (it stays mounted until the font
108
+ // commits, after which `fontStyle` would otherwise retrigger it).
109
+ const submittedFamilyRef = (0, _react.useRef)(null);
110
+
111
+ // While a switch is in flight the pick sets the live variableSettings ahead
112
+ // of the font committing; freezing to the last settled value keeps the style
113
+ // selector showing the active font's instance until they realign.
114
+ const frozenVariableSettingsRef = (0, _react.useRef)(variableSettings);
115
+ const handleOpen = (0, _react.useCallback)(() => {
116
+ loadFamilies();
117
+ loadFamilyStyles(currentFamilyId);
118
+ }, [loadFamilies, loadFamilyStyles, currentFamilyId]);
63
119
  const handleSelectFamilyId = (0, _react.useCallback)(id => {
64
- const family = familyData && familyData.find(family => family.id === id);
65
- if (!family) return;
120
+ if (id === currentFamilyId) {
121
+ // Re-selecting the active family cancels any in-flight switch.
122
+ setPendingFamilyId(null);
123
+ submittedFamilyRef.current = null;
124
+ return;
125
+ }
126
+ loadFamilyStyles(id);
127
+ setPendingFamilyId(id);
128
+ }, [loadFamilyStyles, currentFamilyId]);
129
+ const pendingEntry = pendingFamilyId ? getFamilyStyles(pendingFamilyId) : undefined;
130
+
131
+ // A switch is visibly in flight from the moment the user picks a new family
132
+ // until the font actually commits to it (currentFamilyId catches up).
133
+ const switchingToPending = !!pendingFamilyId && currentFamilyId !== pendingFamilyId;
134
+
135
+ // Once the picked family's styles arrive, choose a matching style and report
136
+ // it upward. We deliberately keep `pendingFamilyId` set (and the busy cue on)
137
+ // until the font actually switches — clearing it here would briefly revert the
138
+ // family dropdown during the changed-styles round-trip.
139
+ (0, _react.useEffect)(() => {
140
+ if (!pendingFamilyId || !pendingEntry || pendingEntry.loading || !pendingEntry.styles) {
141
+ return;
142
+ }
143
+ if (submittedFamilyRef.current === pendingFamilyId) return;
144
+ submittedFamilyRef.current = pendingFamilyId;
145
+ const styles = pendingEntry.styles;
66
146
  let fontStyleId = fontStyle.id;
67
- let variableSettings;
68
- if (family.isVariableFont) {
69
- var _family$fontStyles;
70
- const newFontStyle = (_family$fontStyles = family.fontStyles) === null || _family$fontStyles === void 0 ? void 0 : _family$fontStyles[0];
147
+ let newVariableSettings;
148
+ if (pendingEntry.isVariableFont) {
149
+ const newFontStyle = styles[0];
71
150
  if (newFontStyle) {
72
151
  var _newFontStyle$variabl;
73
152
  fontStyleId = newFontStyle.id;
74
153
  const instance = (_newFontStyle$variabl = newFontStyle.variableInstances) === null || _newFontStyle$variabl === void 0 ? void 0 : _newFontStyle$variabl[0];
75
- if (instance) {
76
- variableSettings = (0, _utils.variableInstanceSettings)(instance);
77
- }
154
+ if (instance) newVariableSettings = (0, _utils.variableInstanceSettings)(instance);
78
155
  }
79
- } else {
80
- var _family$fontStyles2;
81
- const styleWeightDifferences = ((_family$fontStyles2 = family.fontStyles) === null || _family$fontStyles2 === void 0 ? void 0 : _family$fontStyles2.map(right => {
82
- return {
83
- id: right.id,
84
- similarity: computeSimilarity(fontStyle, right)
85
- };
86
- }).filter(_utils.notEmpty)) || [];
87
- fontStyleId = styleWeightDifferences.reduce((closest, current) => {
88
- if (current.similarity > closest.similarity) return current;
89
- return closest;
90
- }).id;
156
+ } else if (styles.length > 0) {
157
+ fontStyleId = styles.map(right => ({
158
+ id: right.id,
159
+ similarity: computeSimilarity(fontStyle, right)
160
+ })).reduce((closest, current) => current.similarity > closest.similarity ? current : closest).id;
91
161
  }
92
162
  onSelectFontStyleValue({
93
163
  fontStyleId,
94
- variableSettings
164
+ variableSettings: newVariableSettings
95
165
  });
96
- }, [familyData, fontStyle]);
97
- const instance = variableSettings ? (_fontStyle$variableIn = fontStyle.variableInstances) === null || _fontStyle$variableIn === void 0 ? void 0 : _fontStyle$variableIn.find(instance => (0, _utils.compareVariableSettings)(instance, variableSettings)) : null;
98
- return config.selectable && familyData !== null && familyData !== void 0 && familyData.length ? /*#__PURE__*/_react.default.createElement(_TypeTesterStyleSelect.default, {
99
- families: familyData,
166
+ }, [pendingFamilyId, pendingEntry, fontStyle, onSelectFontStyleValue]);
167
+
168
+ // The font has actually switched to the picked family drop the pending
169
+ // state, which lifts the busy cue and settles the family dropdown on it.
170
+ (0, _react.useEffect)(() => {
171
+ if (pendingFamilyId && currentFamilyId === pendingFamilyId) {
172
+ setPendingFamilyId(null);
173
+ submittedFamilyRef.current = null;
174
+ }
175
+ }, [currentFamilyId, pendingFamilyId]);
176
+
177
+ // Capture the variableSettings while settled so it can be held frozen for the
178
+ // duration of a switch (see `frozenVariableSettingsRef`).
179
+ (0, _react.useEffect)(() => {
180
+ if (!switchingToPending) frozenVariableSettingsRef.current = variableSettings;
181
+ }, [switchingToPending, variableSettings]);
182
+
183
+ // Family options, filtered so they intersect the current style's languages
184
+ // (e.g. an Arabic-only style only offers families that also support Arabic).
185
+ const familyOptions = (0, _react.useMemo)(() => {
186
+ if (!families) {
187
+ return fontStyle.family ? [{
188
+ id: fontStyle.family.id,
189
+ name: fontStyle.family.name
190
+ }] : [];
191
+ }
192
+ return families.filter(family => {
193
+ var _family$featureStyle, _family$featureStyle$;
194
+ return (_family$featureStyle = family.featureStyle) === null || _family$featureStyle === void 0 ? void 0 : (_family$featureStyle$ = _family$featureStyle.supportedLanguages) === null || _family$featureStyle$ === void 0 ? void 0 : _family$featureStyle$.some(lang => {
195
+ var _fontStyle$supportedL;
196
+ return (_fontStyle$supportedL = fontStyle.supportedLanguages) === null || _fontStyle$supportedL === void 0 ? void 0 : _fontStyle$supportedL.includes(lang);
197
+ });
198
+ }).map(family => ({
199
+ id: family.id,
200
+ name: family.name
201
+ }));
202
+ }, [families, fontStyle.family, fontStyle.supportedLanguages]);
203
+ if (!fontStyle.family) {
204
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, fallbackName);
205
+ }
206
+
207
+ // Once the list has loaded, a zero-language font (no family shares the current
208
+ // style's languages) falls back to the plain name, matching the
209
+ // non-selectable rendering. See FD-628.
210
+ if (families && familyOptions.length === 0) {
211
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, fallbackName);
212
+ }
213
+
214
+ // Style options always reflect the *active* font's family. When a pending
215
+ // switch commits, `fontStyle` updates and this moves to the new family — so
216
+ // the style selector + font transition together with no blank intermediate.
217
+ const selectedFamily = buildSelectedFamily(currentFamilyId, getFamilyStyles(currentFamilyId), fontStyle, families);
218
+
219
+ // Hold the displayed instance on the active font's value during a switch; once
220
+ // it commits, use the live (now-synced) value so axis drags etc. flow through.
221
+ const effectiveVariableSettings = switchingToPending ? frozenVariableSettingsRef.current : variableSettings;
222
+ return /*#__PURE__*/_react.default.createElement(_TypeTesterStyleSelect.default, {
223
+ families: familyOptions,
224
+ selectedFamily: selectedFamily
225
+ // Hold the displayed family on the active font's family until the switch
226
+ // commits, so both selectors + the font flip together. The busy cue is
227
+ // the only immediate feedback while the picked family's styles load.
228
+ ,
229
+ familyValue: currentFamilyId,
100
230
  selectedFontStyleId: fontStyle.id,
231
+ selectedVariableSettings: effectiveVariableSettings,
232
+ loadingFamilies: loadingFamilies && !families,
233
+ loading: switchingToPending,
234
+ onOpen: handleOpen,
235
+ onSelectFamilyId: handleSelectFamilyId,
101
236
  onSelectFontStyleValue: onSelectFontStyleValue,
102
237
  onSelectVariableAxes: onSelectVariableAxes,
103
- onSelectFamilyId: handleSelectFamilyId,
104
- selectedVariableSettings: variableSettings,
105
238
  includeVariableAxesOption: includeVariableAxesOption
106
- }) : fontStyle.family && /*#__PURE__*/_react.default.createElement("div", {
239
+ });
240
+ }
241
+ function TypeTesterStyleSelectData(_ref2) {
242
+ var _fontStyle$variableIn2;
243
+ let {
244
+ fontStyle: fontStyleKey,
245
+ onSelectFontStyleValue,
246
+ onSelectVariableAxes,
247
+ config,
248
+ variableSettings,
249
+ includeVariableAxesOption
250
+ } = _ref2;
251
+ const fontStyle = (0, _reactRelay.useFragment)((_TypeTesterStyleSelectData_fontStyle2.default.hash && _TypeTesterStyleSelectData_fontStyle2.default.hash !== "368fd433966e05fc6ebecd2b60c71e3f" && console.error("The definition of 'TypeTesterStyleSelectData_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStyleSelectData_fontStyle2.default), fontStyleKey);
252
+ const instance = variableSettings ? (_fontStyle$variableIn2 = fontStyle.variableInstances) === null || _fontStyle$variableIn2 === void 0 ? void 0 : _fontStyle$variableIn2.find(inst => (0, _utils.compareVariableSettings)(inst, variableSettings)) : null;
253
+ const name = fontStyle.family ? /*#__PURE__*/_react.default.createElement("div", {
107
254
  className: "type-tester__name"
108
255
  }, /*#__PURE__*/_react.default.createElement("span", {
109
256
  className: "type-tester__name__family"
110
257
  }, fontStyle.family.name), ' ', /*#__PURE__*/_react.default.createElement("span", {
111
258
  className: "type-tester__name__style"
112
- }, variableSettings ? (instance === null || instance === void 0 ? void 0 : instance.name) ?? 'Custom' : fontStyle.name.replace(/ /g, '\xa0')));
259
+ }, variableSettings ? (instance === null || instance === void 0 ? void 0 : instance.name) ?? 'Custom' : fontStyle.name.replace(/ /g, '\xa0'))) : null;
260
+ if (!config.selectable) {
261
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, name);
262
+ }
263
+ return /*#__PURE__*/_react.default.createElement(TypeTesterStyleSelectSelectable, {
264
+ fontStyle: fontStyle,
265
+ variableSettings: variableSettings,
266
+ onSelectFontStyleValue: onSelectFontStyleValue,
267
+ onSelectVariableAxes: onSelectVariableAxes,
268
+ includeVariableAxesOption: includeVariableAxesOption,
269
+ fallbackName: name
270
+ });
113
271
  }
@@ -27,8 +27,10 @@ const TypeTesterVariableAxes = _ref => {
27
27
  if (!variableSettings) return null;
28
28
  if (!axes) return null;
29
29
  const handleChange = (axis, value) => {
30
- variableSettings[axis] = value;
31
- setVariableSettings(variableSettings);
30
+ setVariableSettings({
31
+ ...variableSettings,
32
+ [axis]: value
33
+ });
32
34
  };
33
35
  return /*#__PURE__*/_react.default.createElement("div", {
34
36
  className: "type-tester__variable-axes"
@@ -50,14 +52,25 @@ const TypeTesterVariableAxes = _ref => {
50
52
  } = _ref3;
51
53
  const isSmall = Math.abs(maxValue - minValue) <= 1;
52
54
  const value = variableSettings[axis];
55
+ const format = v => isSmall ? v.toFixed(2) : v.toFixed(0);
56
+
57
+ // Reserve enough room for the widest value the axis can show so the
58
+ // number changing (e.g. 99 → 100, or a decimal flipping sign) never
59
+ // reflows the slider beside it. The widest string is always at one of
60
+ // the endpoints — most digits plus any minus sign. With tabular
61
+ // figures (tnum, set in CSS) each character is a stable 1ch wide.
62
+ const valueWidth = Math.max(format(minValue).length, format(maxValue).length);
53
63
  return /*#__PURE__*/_react.default.createElement("div", {
54
64
  key: axis,
55
65
  className: "type-tester__variable-axes__axis"
56
66
  }, /*#__PURE__*/_react.default.createElement("span", {
57
67
  className: "type-tester__variable-axes__name"
58
68
  }, name), /*#__PURE__*/_react.default.createElement("span", {
59
- className: "type-tester__variable-axes__value"
60
- }, isSmall ? value.toFixed(2) : value.toFixed(0)), /*#__PURE__*/_react.default.createElement("div", {
69
+ className: "type-tester__variable-axes__value",
70
+ style: {
71
+ width: `${valueWidth}ch`
72
+ }
73
+ }, format(value)), /*#__PURE__*/_react.default.createElement("div", {
61
74
  className: "type-tester__variable-axes__slider"
62
75
  }, /*#__PURE__*/_react.default.createElement(_TypeTesterSlider.default, {
63
76
  value: value,
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import { TypeTester_viewer$key } from '../../__generated__/TypeTester_viewer.graphql';
3
2
  import { TypeTester_fontStyle$key } from '../../__generated__/TypeTester_fontStyle.graphql';
4
3
  import { Alignment, TypeTesterAxesPosition } from './types';
5
4
  export interface TypeTesterConfig {
@@ -16,7 +15,7 @@ export interface TypeTesterConfig {
16
15
  toolsPosition?: 'inline' | 'floating';
17
16
  selectButtonStyle?: 'inline' | 'outlined';
18
17
  selectButtonLabel?: string;
19
- truncate?: boolean;
18
+ truncate?: boolean | number;
20
19
  bulletStyle?: 'square' | 'round';
21
20
  priceText?: boolean;
22
21
  autofitOnChange?: boolean;
@@ -67,7 +66,6 @@ export interface TypeTesterBaseProps {
67
66
  }
68
67
  interface TypeTesterProps extends TypeTesterBaseProps {
69
68
  fontStyle: TypeTester_fontStyle$key | null;
70
- viewer: TypeTester_viewer$key;
71
69
  onStyleSelect: (fontStyleId: string) => void;
72
70
  tags?: readonly string[] | null;
73
71
  isLoading?: boolean;
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _TypeTester_viewer2 = _interopRequireDefault(require("../../__generated__/TypeTester_viewer.graphql"));
8
7
  var _TypeTester_fontStyle2 = _interopRequireDefault(require("../../__generated__/TypeTester_fontStyle.graphql"));
9
8
  var _react = _interopRequireWildcard(require("react"));
10
9
  var _reactRelay = require("react-relay");
@@ -20,6 +19,7 @@ var _TypeTesterContent = _interopRequireDefault(require("./TypeTesterContent"));
20
19
  var _TypeTesterState = _interopRequireDefault(require("./TypeTesterState"));
21
20
  var _TypeTesterToolbar = _interopRequireDefault(require("./TypeTesterToolbar"));
22
21
  var _TypeTesterVariableAxes = _interopRequireDefault(require("./TypeTesterVariableAxes"));
22
+ var _hooks = require("../../hooks");
23
23
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
24
24
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
25
25
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -29,7 +29,6 @@ const TypeTester = _ref => {
29
29
  id,
30
30
  productId,
31
31
  fontStyle: fontStyleKey,
32
- viewer: viewerKey,
33
32
  tags,
34
33
  onFocus,
35
34
  onBlur,
@@ -40,7 +39,6 @@ const TypeTester = _ref => {
40
39
  isLoading
41
40
  } = _ref;
42
41
  const fontStyle = (0, _reactRelay.useFragment)((_TypeTester_fontStyle2.default.hash && _TypeTester_fontStyle2.default.hash !== "bf89a32c8a0661f2102bbcf8c6bd1aa7" && console.error("The definition of 'TypeTester_fontStyle' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTester_fontStyle2.default), fontStyleKey);
43
- const viewer = (0, _reactRelay.useFragment)((_TypeTester_viewer2.default.hash && _TypeTester_viewer2.default.hash !== "26c2b3145462407675ab9b202b0d1b4c" && console.error("The definition of 'TypeTester_viewer' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTester_viewer2.default), viewerKey);
44
42
  const contentRef = (0, _react.useRef)(null);
45
43
  const ref = (0, _react.useRef)(null);
46
44
  const [buyButtonIsHovered, setBuyButtonIsHovered] = (0, _react.useState)(false);
@@ -51,6 +49,14 @@ const TypeTester = _ref => {
51
49
  const props = (0, _TypeTesterState.default)({
52
50
  id
53
51
  });
52
+
53
+ // The instance name (e.g. "Regular" → "Custom") is a flex sibling of the
54
+ // inline axis slider, so updating it mid-drag changes its width and shifts
55
+ // the slider. Debounce the value used purely for the name display — the font
56
+ // preview and sliders keep reading the live `props.variableSettings`.
57
+ // Keyed on the font style id so switching fonts snaps the instance name
58
+ // immediately (in sync with the new style) rather than lagging the switch.
59
+ const nameVariableSettings = (0, _hooks.useDebouncedValue)(props.variableSettings, 200, fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.id);
54
60
  (0, _react.useEffect)(() => {
55
61
  if (onFocus && props.focused) onFocus();
56
62
  if (onBlur && !props.focused) onBlur();
@@ -86,8 +92,7 @@ const TypeTester = _ref => {
86
92
  className: "type-tester__toolbar"
87
93
  }, /*#__PURE__*/_react.default.createElement(_TypeTesterStyleSelectData.default, {
88
94
  fontStyle: fontStyle,
89
- variableSettings: props.variableSettings,
90
- viewer: viewer,
95
+ variableSettings: nameVariableSettings,
91
96
  onSelectFontStyleValue: _ref2 => {
92
97
  let {
93
98
  fontStyleId,
@@ -146,12 +151,14 @@ const TypeTester = _ref => {
146
151
  }, _ref3 => {
147
152
  let {
148
153
  loaded,
149
- style
154
+ style,
155
+ verticalMetrics
150
156
  } = _ref3;
151
157
  if (loaded) {
152
158
  return /*#__PURE__*/_react.default.createElement(_TypeTesterContent.default, _extends({}, props, {
153
159
  ref: contentRef,
154
160
  fontStyles: style,
161
+ verticalMetrics: verticalMetrics,
155
162
  truncate: config.truncate,
156
163
  direction: props.direction,
157
164
  min: config.size.min,
@@ -1,6 +1,7 @@
1
1
  import { EditorState } from 'draft-js';
2
2
  import { Alignment } from './types';
3
3
  import { VariableSettings } from '../../utils';
4
+ import { VerticalMetrics } from '../useFontStyle';
4
5
  export type ColumnsConfig = false | {
5
6
  count: 'auto' | number;
6
7
  width: string;
@@ -19,15 +20,16 @@ export interface UseTypeTesterStylerProps {
19
20
  letterSpacing: number;
20
21
  features: string[];
21
22
  focused: boolean;
22
- truncate: boolean;
23
+ truncate: boolean | number;
23
24
  fontStyles: React.CSSProperties;
25
+ verticalMetrics?: VerticalMetrics | null;
24
26
  content: EditorState;
25
27
  contentEdited: boolean;
26
28
  alignment: Alignment;
27
29
  variableSettings: VariableSettings | null;
28
30
  columns: ColumnsConfig;
29
31
  }
30
- declare const useTypeTesterStyler: ({ size, autofit, autofitOnChange, features, setSize, min, max, truncate, focused, lineHeight, letterSpacing, fontStyles, content, contentEdited, alignment, variableSettings, columns, }: UseTypeTesterStylerProps) => {
32
+ declare const useTypeTesterStyler: ({ size, autofit, autofitOnChange, features, setSize, min, max, truncate, focused, lineHeight, letterSpacing, fontStyles, verticalMetrics, content, contentEdited, alignment, variableSettings, columns, }: UseTypeTesterStylerProps) => {
31
33
  ref: import("react").RefObject<HTMLDivElement | null>;
32
34
  style: import("react").CSSProperties;
33
35
  };
@@ -21,6 +21,7 @@ const useTypeTesterStyler = _ref => {
21
21
  lineHeight,
22
22
  letterSpacing,
23
23
  fontStyles,
24
+ verticalMetrics,
24
25
  content,
25
26
  contentEdited,
26
27
  alignment,
@@ -57,23 +58,89 @@ const useTypeTesterStyler = _ref => {
57
58
  });
58
59
  }
59
60
  }, [autofitSize]);
60
- const shouldTruncate = truncate && !focused;
61
+
62
+ // `truncate` is `true` (1 line), a positive number (that many lines), or
63
+ // falsy (off). Editing always shows the full paragraph, so only truncate
64
+ // when the tester is not focused.
65
+ const truncateLines = truncate === true ? 1 : typeof truncate === 'number' ? Math.max(0, Math.floor(truncate)) : 0;
66
+ const shouldTruncate = truncateLines > 0 && !focused;
67
+
68
+ // How far a line's ink spills past its line-box, top and bottom, as a fraction
69
+ // of the font-size (em). `fontLoader` feeds these exact metrics into the
70
+ // @font-face as ascent/descent-override, so this matches the browser's line
71
+ // geometry precisely. It is 0 once the line-height is at least the font's
72
+ // natural content height ((ascender − descender) / unitsPerEm) — i.e. for any
73
+ // normal reading line-height — and only grows for tight, overlapping setting.
74
+ // Kept in em (not px) so it scales with the *rendered* size, including the
75
+ // `--type-tester--adjustment` that shrinks the preview on mobile.
76
+ const lineOverflowEm = shouldTruncate && verticalMetrics && verticalMetrics.unitsPerEm > 0 ? Math.max(0, ((verticalMetrics.ascender - verticalMetrics.descender) / verticalMetrics.unitsPerEm - lineHeight) / 2) : 0;
77
+
78
+ // Columns are what make truncation overflow flow sideways — into clipped,
79
+ // off-screen columns — instead of stacking below the last visible line. That
80
+ // is what lets the metric padding reveal descenders/ascenders cleanly, and it
81
+ // means a multi-column tester (e.g. a 3-column specimen) truncates to
82
+ // `truncateLines` lines *per column*. When truncating with columns disabled we
83
+ // still establish a single column so the clipped overflow behaves the same way.
84
+ const columnStyle = columns ? {
85
+ columnCount: columns.count,
86
+ columnWidth: columns.width,
87
+ columnGap: columns.gap
88
+ } : undefined;
89
+
90
+ // A glyph's side bearings — ink that sits outside its advance width, like a
91
+ // leading lowercase 'j' or an italic overhang — would be shorn by the inline
92
+ // clip. Per-glyph bearings aren't in the font metrics, so reserve a flat 0.1em
93
+ // on each inline edge and cancel it with a negative margin so
94
+ // wrapping and text position stay put.
95
+ const sideBearing = shouldTruncate ? '0.1em' : '0px';
61
96
  const style = {
62
97
  fontSize: autofit ? `${size}px` : `calc(var(--type-tester--adjustment, 1) * ${size}px)`,
63
98
  lineHeight: `${lineHeight}`,
64
- height: shouldTruncate ? `${size * lineHeight}px` : 'auto',
65
- overflow: shouldTruncate ? 'hidden' : 'visible',
99
+ ...(shouldTruncate ? {
100
+ // Cap each column at `truncateLines` lines. `max-height` (not `height`)
101
+ // lets shorter paragraphs collapse to their content instead of padding
102
+ // out to N lines of whitespace. Columns send the clipped overflow
103
+ // sideways (into off-screen columns) rather than below, so the last
104
+ // visible line of every column is never partially covered by the next
105
+ // one. That lets the symmetric `lineOverflowEm` padding fully reveal the
106
+ // first line's ascenders/accents and the last line's descenders at any
107
+ // line-height; the negative margins cancel that padding in layout so a
108
+ // collapsed paragraph still occupies exactly its line count.
109
+ //
110
+ // Everything is in `em` so it tracks the rendered font-size (including
111
+ // the mobile `--type-tester--adjustment`); the `+ 2px` is slack that
112
+ // keeps sub-pixel rounding from dropping the Nth line (the extra never
113
+ // reveals line N+1 because that line is clipped out to the side).
114
+ boxSizing: 'border-box',
115
+ maxHeight: `calc(${truncateLines * lineHeight + 2 * lineOverflowEm}em + 2px)`,
116
+ paddingTop: `${lineOverflowEm}em`,
117
+ paddingBottom: `${lineOverflowEm}em`,
118
+ paddingLeft: sideBearing,
119
+ paddingRight: sideBearing,
120
+ marginTop: `${-lineOverflowEm}em`,
121
+ marginBottom: `${-lineOverflowEm}em`,
122
+ overflow: 'hidden',
123
+ // Fill each column to its full N lines before spilling to the next, and
124
+ // allow a final column with a single line — otherwise the default
125
+ // `widows: 2` / `column-fill: balance` rebalance a 4-line paragraph into
126
+ // two columns of 2, showing N−1 lines instead of N.
127
+ columnFill: 'auto',
128
+ widows: 1,
129
+ orphans: 1,
130
+ ...(columnStyle ?? {
131
+ columnCount: 1
132
+ })
133
+ } : {
134
+ height: 'auto',
135
+ overflow: 'visible',
136
+ ...columnStyle
137
+ }),
66
138
  fontFeatureSettings,
67
- marginRight: alignment !== 'right' ? -sideBuffer : 0,
68
- marginLeft: alignment === 'right' ? -sideBuffer : 0,
139
+ marginRight: `calc(${alignment !== 'right' ? -sideBuffer : 0}px - ${sideBearing})`,
140
+ marginLeft: `calc(${alignment === 'right' ? -sideBuffer : 0}px - ${sideBearing})`,
69
141
  letterSpacing: `${letterSpacing}em`,
70
142
  fontVariationSettings,
71
- textAlign: alignment,
72
- ...(columns && {
73
- columnCount: columns.count,
74
- columnWidth: columns.width,
75
- columnGap: columns.gap
76
- })
143
+ textAlign: alignment
77
144
  };
78
145
  return {
79
146
  ref,