fontdue-js 3.0.0-alpha8 → 3.0.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 (146) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +253 -1
  3. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
  4. package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +9 -3
  5. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
  6. package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +9 -3
  7. package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
  8. package/dist/__generated__/CartOrderUpdateMutation.graphql.js +9 -3
  9. package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
  10. package/dist/__generated__/CartQuery.graphql.js +9 -3
  11. package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
  12. package/dist/__generated__/CartStateUpdateMutation.graphql.js +9 -3
  13. package/dist/__generated__/CharacterViewerIDQuery.graphql.d.ts +1 -1
  14. package/dist/__generated__/CharacterViewerIDQuery.graphql.js +9 -3
  15. package/dist/__generated__/CharacterViewerSlugQuery.graphql.d.ts +1 -1
  16. package/dist/__generated__/CharacterViewerSlugQuery.graphql.js +9 -3
  17. package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.d.ts +1 -1
  18. package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +9 -3
  19. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
  20. package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +9 -3
  21. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
  22. package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +9 -3
  23. package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
  24. package/dist/__generated__/CollectionAa_Query.graphql.js +9 -3
  25. package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +1 -1
  26. package/dist/__generated__/FontFamiliesQuery.graphql.js +9 -3
  27. package/dist/__generated__/FontdueAdminToolbarQuery.graphql.d.ts +20 -0
  28. package/dist/__generated__/FontdueAdminToolbarQuery.graphql.js +80 -0
  29. package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.d.ts +18 -0
  30. package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.js +56 -0
  31. package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
  32. package/dist/__generated__/PrecartAddToCartMutation.graphql.js +9 -3
  33. package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
  34. package/dist/__generated__/StoreModalCartQuery.graphql.js +9 -3
  35. package/dist/__generated__/StoreModalContainerQuery.graphql.d.ts +1 -1
  36. package/dist/__generated__/StoreModalContainerQuery.graphql.js +9 -3
  37. package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +1 -1
  38. package/dist/__generated__/StoreModalIndexQuery.graphql.js +9 -3
  39. package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
  40. package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -3
  41. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
  42. package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -3
  43. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
  44. package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +9 -3
  45. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
  46. package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +9 -3
  47. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -1
  48. package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +9 -3
  49. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
  50. package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +9 -3
  51. package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
  52. package/dist/__generated__/TypeTestersIDQuery.graphql.js +9 -3
  53. package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
  54. package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +9 -3
  55. package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
  56. package/dist/__generated__/TypeTestersSlugQuery.graphql.js +9 -3
  57. package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +1 -8
  58. package/dist/__generated__/useFontStyle_fontStyle.graphql.d.ts +2 -1
  59. package/dist/__generated__/useFontStyle_fontStyle.graphql.js +8 -2
  60. package/dist/__tests__/createFontdueFetch.test.js +276 -0
  61. package/dist/__tests__/imageLoader.test.js +62 -0
  62. package/dist/__tests__/metricFallback.test.js +74 -0
  63. package/dist/__tests__/networkFetch.test.js +188 -0
  64. package/dist/__tests__/nextAdapter.test.js +273 -18
  65. package/dist/__tests__/preview.test.js +217 -0
  66. package/dist/__tests__/previewServer.test.js +118 -0
  67. package/dist/__tests__/previewState.test.js +63 -0
  68. package/dist/__tests__/serverConfig.test.js +62 -0
  69. package/dist/components/BuyButton/index.d.ts +2 -2
  70. package/dist/components/BuyButton/index.js +3 -3
  71. package/dist/components/Cart/CartOrder.js +9 -1
  72. package/dist/components/Cart/orderTracking.js +8 -15
  73. package/dist/components/CharacterViewer/index.d.ts +2 -2
  74. package/dist/components/CharacterViewer/index.js +20 -11
  75. package/dist/components/ConfigContext.d.ts +21 -2
  76. package/dist/components/ConfigContext.js +12 -2
  77. package/dist/components/ConnectionErrorToolbar.d.ts +1 -0
  78. package/dist/components/ConnectionErrorToolbar.js +106 -0
  79. package/dist/components/FontStyle/index.d.ts +2 -0
  80. package/dist/components/FontStyle/index.js +4 -2
  81. package/dist/components/FontdueAdminToolbar/index.d.ts +2 -0
  82. package/dist/components/FontdueAdminToolbar/index.js +299 -0
  83. package/dist/components/FontdueAdminToolbar/previewState.d.ts +7 -0
  84. package/dist/components/FontdueAdminToolbar/previewState.js +58 -0
  85. package/dist/components/FontdueContextProvider/index.js +4 -2
  86. package/dist/components/FontdueProvider/index.js +6 -1
  87. package/dist/components/FontdueProvider/index.server.d.ts +1 -0
  88. package/dist/components/FontdueProvider/index.server.js +10 -0
  89. package/dist/components/NewsletterSignup/index.d.ts +2 -2
  90. package/dist/components/NewsletterSignup/index.js +2 -2
  91. package/dist/components/Root/index.js +16 -2
  92. package/dist/components/TestFontsForm/index.d.ts +2 -2
  93. package/dist/components/TestFontsForm/index.js +2 -2
  94. package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -2
  95. package/dist/components/TypeTester/TypeTesterStandalone.js +2 -2
  96. package/dist/components/TypeTester/index.js +3 -1
  97. package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
  98. package/dist/components/TypeTester/useTypeTesterStyler.js +70 -20
  99. package/dist/components/TypeTesters/index.d.ts +2 -2
  100. package/dist/components/TypeTesters/index.js +3 -3
  101. package/dist/components/elements/StoreModalUnifiedCheckout.js +8 -0
  102. package/dist/components/useFontStyle.d.ts +8 -0
  103. package/dist/components/useFontStyle.js +14 -4
  104. package/dist/corsError.d.ts +1 -5
  105. package/dist/corsError.js +23 -13
  106. package/dist/data/unicodeNamesUrl.d.ts +2 -0
  107. package/dist/data/unicodeNamesUrl.js +18 -0
  108. package/dist/data/unicodeNamesVersion.d.ts +1 -0
  109. package/dist/data/unicodeNamesVersion.js +4 -0
  110. package/dist/fallbackFontData.d.ts +2 -0
  111. package/dist/fallbackFontData.js +10 -0
  112. package/dist/fontdue.css +231 -4
  113. package/dist/loadFontdueProviderQuery.d.ts +2 -1
  114. package/dist/loadFontdueProviderQuery.js +5 -2
  115. package/dist/metricFallback.d.ts +48 -0
  116. package/dist/metricFallback.js +98 -0
  117. package/dist/next/config.d.ts +1 -5
  118. package/dist/next/config.js +14 -1
  119. package/dist/next/image-loader.js +22 -3
  120. package/dist/next/index.d.ts +1 -2
  121. package/dist/next/index.js +14 -6
  122. package/dist/next/registerSingleTenantResolver.d.ts +1 -0
  123. package/dist/next/registerSingleTenantResolver.js +35 -0
  124. package/dist/next/revalidate.js +1 -1
  125. package/dist/next/tenant.d.ts +10 -2
  126. package/dist/next/tenant.js +111 -16
  127. package/dist/preview/constants.d.ts +9 -0
  128. package/dist/preview/constants.js +117 -0
  129. package/dist/preview/index.d.ts +53 -0
  130. package/dist/preview/index.js +190 -0
  131. package/dist/preview/server.d.ts +20 -0
  132. package/dist/preview/server.js +89 -0
  133. package/dist/relay/environment.d.ts +8 -0
  134. package/dist/relay/environment.js +81 -25
  135. package/dist/relay/loadSerializableQuery.d.ts +13 -3
  136. package/dist/relay/loadSerializableQuery.js +2 -0
  137. package/dist/relay/serverConfig.d.ts +5 -1
  138. package/dist/relay/serverConfig.js +83 -8
  139. package/dist/scripts/publishUnicodeData.js +68 -0
  140. package/dist/scripts/updateUnicodeData.js +41 -6
  141. package/dist/server/index.d.ts +37 -0
  142. package/dist/server/index.js +160 -0
  143. package/package.json +5 -1
  144. package/types/next-headers.d.ts +9 -0
  145. package/types/next-navigation.d.ts +10 -0
  146. package/vitest.config.ts +9 -0
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { FontdueProvider_props } from './index.js';
3
+ import '../../next/registerSingleTenantResolver.js';
3
4
  export type { FontdueProvider_props } from './index.js';
4
5
  export type { FontdueProviderPreloadedQuery } from '../../loadFontdueProviderQuery.js';
5
6
  export type { FontdueServerConfig } from '../../relay/serverConfig.js';
@@ -3,6 +3,16 @@ import React from 'react';
3
3
  import FontdueProvider from './index.js';
4
4
  import loadFontdueProviderQueryImpl from '../../loadFontdueProviderQuery.js';
5
5
  import { setFontdueServerConfig } from '../../relay/serverConfig.js';
6
+ // Side-effect import: registers the single-tenant ambient config resolver so a
7
+ // single-tenant foundry that mounts <FontdueProvider> never has to call a
8
+ // per-render setup function. The module statically pulls in only browser-safe
9
+ // code and lazy-loads the Next request APIs (next/headers, next/navigation)
10
+ // behind a dynamic import that runs only when the resolver resolves config —
11
+ // keeping next/* off this react-server entrypoint's eager graph. The resolver
12
+ // no-ops in multi-tenant mode (which drives config through the slot) and is
13
+ // inert in the default build Astro/RR7 use. See
14
+ // ../../next/registerSingleTenantResolver.ts.
15
+ import '../../next/registerSingleTenantResolver.js';
6
16
  // Stub for the RSC export condition. The default `<FontdueProvider>` server
7
17
  // entrypoint (this file) awaits the query for consumers, so RSC users should
8
18
  // never call it manually. Re-exporting a throwing stub makes the mistake
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
- import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
2
+ import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
3
3
  import { NewsletterSignupQuery } from '../../__generated__/NewsletterSignupQuery.graphql.js';
4
4
  import { Config } from '../ConfigContext.js';
5
5
  export type NewsletterSignupPreloadedQuery = SerializablePreloadedQuery<NewsletterSignupQuery>;
6
- export declare function loadNewsletterSignupQuery(): Promise<NewsletterSignupPreloadedQuery>;
6
+ export declare function loadNewsletterSignupQuery(options?: LoadQueryOptions): Promise<NewsletterSignupPreloadedQuery>;
7
7
  export interface NewsletterSignup_props {
8
8
  optInLabel?: string;
9
9
  successLabel?: string;
@@ -13,8 +13,8 @@ import useSerializablePreloadedQuery from '../../relay/useSerializablePreloadedQ
13
13
  import NewsletterSignupQueryNode from '../../__generated__/NewsletterSignupQuery.graphql.js';
14
14
  import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
15
15
  const updateCustomerMutation = (_NewsletterSignupUpdateCustomerMutation.hash && _NewsletterSignupUpdateCustomerMutation.hash !== "769087891b6f263122bbb630b3f2ca6c" && console.error("The definition of 'NewsletterSignupUpdateCustomerMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NewsletterSignupUpdateCustomerMutation);
16
- export async function loadNewsletterSignupQuery() {
17
- return loadSerializableQuery(NewsletterSignupQueryNode, {});
16
+ export async function loadNewsletterSignupQuery(options) {
17
+ return loadSerializableQuery(NewsletterSignupQueryNode, {}, options);
18
18
  }
19
19
  const query = (_NewsletterSignupQuery.hash && _NewsletterSignupQuery.hash !== "24b303198a6038318723fc0124548862" && console.error("The definition of 'NewsletterSignupQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NewsletterSignupQuery);
20
20
  function NewsletterSignupComponent(_ref) {
@@ -71,11 +71,25 @@ const getInitialElements = () => {
71
71
  return map;
72
72
  };
73
73
  const Root = props => {
74
+ var _props$config;
75
+ // The script-tag integration is client-only — there is no server render to
76
+ // forward a preview token to — so the admin toolbar must enter/exit preview
77
+ // by toggling the marker cookie in the browser. Force it on here, where the
78
+ // integration is unambiguously client-side. (The admin's Fontdue session
79
+ // rides the credentialed cross-origin GraphQL fetch and gates the reveal.)
80
+ const config = {
81
+ ...props.config,
82
+ preview: {
83
+ ...((_props$config = props.config) === null || _props$config === void 0 ? void 0 : _props$config.preview),
84
+ clientSide: true
85
+ }
86
+ };
87
+
74
88
  // use a Map here instead of an array in case we register the same element
75
89
  // more than once (otherwise we run into issues rendering the component
76
90
  // twice into the same element). this also gives us a way to set unique keys.
77
91
  const [elements, setElements] = useState(getInitialElements());
78
- const store = useRef(createDefaultStore(props.config));
92
+ const store = useRef(createDefaultStore(config));
79
93
  useEffect(() => {
80
94
  // watch for any new or removed fontdue elements
81
95
 
@@ -190,7 +204,7 @@ const Root = props => {
190
204
  return /*#__PURE__*/React.createElement(FontdueProvider, {
191
205
  url: props.url,
192
206
  stripeIntegration: props.stripeIntegration,
193
- config: props.config,
207
+ config: config,
194
208
  components: props.components,
195
209
  store: store.current
196
210
  }, /*#__PURE__*/React.createElement(React.Fragment, null, Array.from(elements).map(_ref => {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { TestFontsForm_Query } from '../../__generated__/TestFontsForm_Query.graphql.js';
3
- import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
3
+ import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
4
4
  import { Config } from '../ConfigContext.js';
5
5
  export interface TestFontsForm_props {
6
6
  agreementLabel?: string;
@@ -8,7 +8,7 @@ export interface TestFontsForm_props {
8
8
  newsletterCheckboxChecked?: boolean;
9
9
  }
10
10
  export type TestFontsFormPreloadedQuery = SerializablePreloadedQuery<TestFontsForm_Query>;
11
- export declare function loadTestFontsFormQuery(): Promise<TestFontsFormPreloadedQuery>;
11
+ export declare function loadTestFontsFormQuery(options?: LoadQueryOptions): Promise<TestFontsFormPreloadedQuery>;
12
12
  export declare function TestFontsFormPreloadedQueryRenderer({ preloadedQuery, ...rest }: TestFontsForm_props & {
13
13
  preloadedQuery: TestFontsFormPreloadedQuery;
14
14
  }): React.JSX.Element;
@@ -194,8 +194,8 @@ const TestFontsFormComponent = _ref2 => {
194
194
  }));
195
195
  };
196
196
  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);
197
- export async function loadTestFontsFormQuery() {
198
- return loadSerializableQuery(TestFontsFormQueryNode, {});
197
+ export async function loadTestFontsFormQuery(options) {
198
+ return loadSerializableQuery(TestFontsFormQueryNode, {}, options);
199
199
  }
200
200
  export function TestFontsFormPreloadedQueryRenderer(_ref4) {
201
201
  let {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Alignment, Direction, FeaturesProp } from './types.js';
3
- import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
3
+ import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
4
4
  import { TypeTesterStandaloneQuery } from '../../__generated__/TypeTesterStandaloneQuery.graphql.js';
5
5
  import { Config } from '../ConfigContext.js';
6
6
  interface TypeTesterStandaloneComponent_props {
@@ -30,7 +30,7 @@ export interface LoadTypeTesterQueryVariables {
30
30
  familyName: string;
31
31
  styleName: string;
32
32
  }
33
- export declare function loadTypeTesterQuery(variables: LoadTypeTesterQueryVariables): Promise<TypeTesterPreloadedQuery>;
33
+ export declare function loadTypeTesterQuery(variables: LoadTypeTesterQueryVariables, options?: LoadQueryOptions): Promise<TypeTesterPreloadedQuery>;
34
34
  export declare function TypeTesterStandalonePreloadedQueryRenderer({ preloadedQuery, ...rest }: TypeTesterStandaloneComponent_props & {
35
35
  preloadedQuery: TypeTesterPreloadedQuery;
36
36
  }): React.JSX.Element;
@@ -100,11 +100,11 @@ function TypeTesterStandaloneComponent(_ref) {
100
100
  }));
101
101
  }
102
102
  const query = (_TypeTesterStandaloneQuery.hash && _TypeTesterStandaloneQuery.hash !== "951214eacb2370ea3ca0b19bebe7fbe7" && console.error("The definition of 'TypeTesterStandaloneQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStandaloneQuery);
103
- export async function loadTypeTesterQuery(variables) {
103
+ export async function loadTypeTesterQuery(variables, options) {
104
104
  return loadSerializableQuery(TypeTesterStandaloneQueryNode, {
105
105
  familyName: variables.familyName,
106
106
  styleName: variables.styleName
107
- });
107
+ }, options);
108
108
  }
109
109
  export function TypeTesterStandalonePreloadedQueryRenderer(_ref3) {
110
110
  let {
@@ -142,12 +142,14 @@ const TypeTester = _ref => {
142
142
  }, _ref3 => {
143
143
  let {
144
144
  loaded,
145
- style
145
+ style,
146
+ verticalMetrics
146
147
  } = _ref3;
147
148
  if (loaded) {
148
149
  return /*#__PURE__*/React.createElement(TypeTesterContent, _extends({}, props, {
149
150
  ref: contentRef,
150
151
  fontStyles: style,
152
+ verticalMetrics: verticalMetrics,
151
153
  truncate: config.truncate,
152
154
  direction: props.direction,
153
155
  min: config.size.min,
@@ -1,6 +1,7 @@
1
1
  import { EditorState } from 'draft-js';
2
2
  import { Alignment } from './types.js';
3
3
  import { VariableSettings } from '../../utils.js';
4
+ import { VerticalMetrics } from '../useFontStyle.js';
4
5
  export type ColumnsConfig = false | {
5
6
  count: 'auto' | number;
6
7
  width: string;
@@ -21,13 +22,14 @@ export interface UseTypeTesterStylerProps {
21
22
  focused: boolean;
22
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
  };
@@ -15,6 +15,7 @@ const useTypeTesterStyler = _ref => {
15
15
  lineHeight,
16
16
  letterSpacing,
17
17
  fontStyles,
18
+ verticalMetrics,
18
19
  content,
19
20
  contentEdited,
20
21
  alignment,
@@ -58,33 +59,82 @@ const useTypeTesterStyler = _ref => {
58
59
  const truncateLines = truncate === true ? 1 : typeof truncate === 'number' ? Math.max(0, Math.floor(truncate)) : 0;
59
60
  const shouldTruncate = truncateLines > 0 && !focused;
60
61
 
61
- // The trailing ellipsis needs `-webkit-line-clamp`, which can't coexist with
62
- // CSS multi-column. `columns` is set by default, so the ellipsis is only
63
- // available in single-column mode; otherwise the text is hard-clipped.
64
- const ellipsis = shouldTruncate && !columns;
62
+ // How far a line's ink spills past its line-box, top and bottom, as a fraction
63
+ // of the font-size (em). `fontLoader` feeds these exact metrics into the
64
+ // @font-face as ascent/descent-override, so this matches the browser's line
65
+ // geometry precisely. It is 0 once the line-height is at least the font's
66
+ // natural content height ((ascender − descender) / unitsPerEm) — i.e. for any
67
+ // normal reading line-height — and only grows for tight, overlapping setting.
68
+ // Kept in em (not px) so it scales with the *rendered* size, including the
69
+ // `--type-tester--adjustment` that shrinks the preview on mobile.
70
+ const lineOverflowEm = shouldTruncate && verticalMetrics && verticalMetrics.unitsPerEm > 0 ? Math.max(0, ((verticalMetrics.ascender - verticalMetrics.descender) / verticalMetrics.unitsPerEm - lineHeight) / 2) : 0;
71
+
72
+ // Columns are what make truncation overflow flow sideways — into clipped,
73
+ // off-screen columns — instead of stacking below the last visible line. That
74
+ // is what lets the metric padding reveal descenders/ascenders cleanly, and it
75
+ // means a multi-column tester (e.g. a 3-column specimen) truncates to
76
+ // `truncateLines` lines *per column*. When truncating with columns disabled we
77
+ // still establish a single column so the clipped overflow behaves the same way.
78
+ const columnStyle = columns ? {
79
+ columnCount: columns.count,
80
+ columnWidth: columns.width,
81
+ columnGap: columns.gap
82
+ } : undefined;
83
+
84
+ // A glyph's side bearings — ink that sits outside its advance width, like a
85
+ // leading lowercase 'j' or an italic overhang — would be shorn by the inline
86
+ // clip. Per-glyph bearings aren't in the font metrics, so reserve a flat 0.1em
87
+ // on each inline edge and cancel it with a negative margin so
88
+ // wrapping and text position stay put.
89
+ const sideBearing = shouldTruncate ? '0.1em' : '0px';
65
90
  const style = {
66
91
  fontSize: autofit ? `${size}px` : `calc(var(--type-tester--adjustment, 1) * ${size}px)`,
67
92
  lineHeight: `${lineHeight}`,
68
- // Reserve exactly `truncateLines` lines so the height is predictable and
69
- // side-by-side testers keep their toolbars aligned, then clip the overflow.
70
- height: shouldTruncate ? `${size * lineHeight * truncateLines}px` : 'auto',
71
- overflow: shouldTruncate ? 'hidden' : 'visible',
72
- ...(ellipsis && {
73
- display: '-webkit-box',
74
- WebkitBoxOrient: 'vertical',
75
- WebkitLineClamp: truncateLines
93
+ ...(shouldTruncate ? {
94
+ // Cap each column at `truncateLines` lines. `max-height` (not `height`)
95
+ // lets shorter paragraphs collapse to their content instead of padding
96
+ // out to N lines of whitespace. Columns send the clipped overflow
97
+ // sideways (into off-screen columns) rather than below, so the last
98
+ // visible line of every column is never partially covered by the next
99
+ // one. That lets the symmetric `lineOverflowEm` padding fully reveal the
100
+ // first line's ascenders/accents and the last line's descenders at any
101
+ // line-height; the negative margins cancel that padding in layout so a
102
+ // collapsed paragraph still occupies exactly its line count.
103
+ //
104
+ // Everything is in `em` so it tracks the rendered font-size (including
105
+ // the mobile `--type-tester--adjustment`); the `+ 2px` is slack that
106
+ // keeps sub-pixel rounding from dropping the Nth line (the extra never
107
+ // reveals line N+1 because that line is clipped out to the side).
108
+ boxSizing: 'border-box',
109
+ maxHeight: `calc(${truncateLines * lineHeight + 2 * lineOverflowEm}em + 2px)`,
110
+ paddingTop: `${lineOverflowEm}em`,
111
+ paddingBottom: `${lineOverflowEm}em`,
112
+ paddingLeft: sideBearing,
113
+ paddingRight: sideBearing,
114
+ marginTop: `${-lineOverflowEm}em`,
115
+ marginBottom: `${-lineOverflowEm}em`,
116
+ overflow: 'hidden',
117
+ // Fill each column to its full N lines before spilling to the next, and
118
+ // allow a final column with a single line — otherwise the default
119
+ // `widows: 2` / `column-fill: balance` rebalance a 4-line paragraph into
120
+ // two columns of 2, showing N−1 lines instead of N.
121
+ columnFill: 'auto',
122
+ widows: 1,
123
+ orphans: 1,
124
+ ...(columnStyle ?? {
125
+ columnCount: 1
126
+ })
127
+ } : {
128
+ height: 'auto',
129
+ overflow: 'visible',
130
+ ...columnStyle
76
131
  }),
77
132
  fontFeatureSettings,
78
- marginRight: alignment !== 'right' ? -sideBuffer : 0,
79
- marginLeft: alignment === 'right' ? -sideBuffer : 0,
133
+ marginRight: `calc(${alignment !== 'right' ? -sideBuffer : 0}px - ${sideBearing})`,
134
+ marginLeft: `calc(${alignment === 'right' ? -sideBuffer : 0}px - ${sideBearing})`,
80
135
  letterSpacing: `${letterSpacing}em`,
81
136
  fontVariationSettings,
82
- textAlign: alignment,
83
- ...(columns && {
84
- columnCount: columns.count,
85
- columnWidth: columns.width,
86
- columnGap: columns.gap
87
- })
137
+ textAlign: alignment
88
138
  };
89
139
  return {
90
140
  ref,
@@ -3,7 +3,7 @@ import { TypeTestersIDQuery } from '../../__generated__/TypeTestersIDQuery.graph
3
3
  import { TypeTestersSlugQuery } from '../../__generated__/TypeTestersSlugQuery.graphql.js';
4
4
  import { Config } from '../ConfigContext.js';
5
5
  import { FeaturesProp } from '../TypeTester/types.js';
6
- import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
6
+ import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
7
7
  export interface TypeTesters_props {
8
8
  collectionId?: string;
9
9
  collectionSlug?: string;
@@ -33,7 +33,7 @@ export type LoadTypeTestersQueryVariables = ({
33
33
  tags?: string[] | null;
34
34
  excludeTags?: string[] | null;
35
35
  };
36
- export declare function loadTypeTestersQuery(variables: LoadTypeTestersQueryVariables): Promise<TypeTestersPreloadedQuery>;
36
+ export declare function loadTypeTestersQuery(variables: LoadTypeTestersQueryVariables, options?: LoadQueryOptions): Promise<TypeTestersPreloadedQuery>;
37
37
  export type TypeTesters_unifiedProps = TypeTesters_props & {
38
38
  preloadedQuery?: TypeTestersPreloadedQuery;
39
39
  config?: Config;
@@ -251,7 +251,7 @@ function TypeTestersSlugQueryRenderer(_ref6) {
251
251
  excludeTags: excludeTags
252
252
  }));
253
253
  }
254
- export async function loadTypeTestersQuery(variables) {
254
+ export async function loadTypeTestersQuery(variables, options) {
255
255
  const tags = variables.tags ?? null;
256
256
  const excludeTags = variables.excludeTags ?? null;
257
257
  if (variables.collectionId) {
@@ -259,14 +259,14 @@ export async function loadTypeTestersQuery(variables) {
259
259
  collectionId: variables.collectionId,
260
260
  tags,
261
261
  excludeTags
262
- });
262
+ }, options);
263
263
  }
264
264
  if (variables.collectionSlug) {
265
265
  return loadSerializableQuery(TypeTestersSlugQueryNode, {
266
266
  collectionSlug: variables.collectionSlug,
267
267
  tags,
268
268
  excludeTags
269
- });
269
+ }, options);
270
270
  }
271
271
  throw new Error('loadTypeTestersQuery expected either collectionId or collectionSlug');
272
272
  }
@@ -75,6 +75,7 @@ import StripeLogo from '../StripeLogo.js';
75
75
  import { Check, X } from '../Icons/index.js';
76
76
  import ConfigContext from '../ConfigContext.js';
77
77
  import { Price } from '../Price/index.js';
78
+ import { sendOrderTracking } from '../Cart/orderTracking.js';
78
79
 
79
80
  /* const LICENSEE_REQUIRED_FIELDS = ['organization', 'country'] as (keyof Identity)[]; */
80
81
 
@@ -274,6 +275,13 @@ export default function StoreModalUnifiedCheckout(_ref2) {
274
275
  }));
275
276
  }, [setLicenseeIdentity]);
276
277
  const environment = useRelayEnvironment();
278
+
279
+ // Capture analytics consent + attribution on the order for the purchase
280
+ // event the server emits on completion (from a Stripe webhook, where no
281
+ // browser context exists).
282
+ useEffect(() => {
283
+ sendOrderTracking(environment);
284
+ }, [environment]);
277
285
  const [error, setError] = useState(null);
278
286
  const [errorsObject, setErrorsObject] = useState(null);
279
287
  const [customerErrorsObject, setCustomerErrorsObject] = useState(null);
@@ -3,8 +3,16 @@ import { useFontStyle_fontStyle$key } from '../__generated__/useFontStyle_fontSt
3
3
  interface UseFontStyle_props {
4
4
  fontStyle: useFontStyle_fontStyle$key | null | undefined;
5
5
  }
6
+ export type VerticalMetrics = {
7
+ readonly unitsPerEm: number;
8
+ readonly ascender: number;
9
+ readonly descender: number;
10
+ readonly lineGap: number | null;
11
+ readonly avgCharWidth: number | null;
12
+ };
6
13
  declare const useFontStyle: ({ fontStyle: fontStyleKey, }: UseFontStyle_props) => {
7
14
  style: React.CSSProperties;
8
15
  loaded: boolean;
16
+ verticalMetrics: VerticalMetrics | null;
9
17
  };
10
18
  export default useFontStyle;
@@ -2,12 +2,13 @@ import _useFontStyle_fontStyle from "../__generated__/useFontStyle_fontStyle.gra
2
2
  import { useState, useRef, useEffect } from 'react';
3
3
  import { graphql, useFragment } from 'react-relay';
4
4
  import { loadFont, isFontLoaded } from '../fontLoader.js';
5
+ import { ensureMetricFallback, metricFallbackFamily } from '../metricFallback.js';
5
6
  const FALLBACK_FAMILY = 'Fallback';
6
7
  const useFontStyle = _ref => {
7
8
  let {
8
9
  fontStyle: fontStyleKey
9
10
  } = _ref;
10
- const fontStyle = useFragment((_useFontStyle_fontStyle.hash && _useFontStyle_fontStyle.hash !== "df4c7160c29373e86bea8b6082d19994" && 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);
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);
11
12
  const fontFamily = fontStyle ? `${fontStyle.cssFamily} ${fontStyle.name}` : null;
12
13
  const sources = fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.webfontSources;
13
14
  const verticalMetrics = fontStyle === null || fontStyle === void 0 ? void 0 : fontStyle.verticalMetrics;
@@ -24,6 +25,10 @@ const useFontStyle = _ref => {
24
25
  }, []);
25
26
  useEffect(() => {
26
27
  if (!fontFamily) return;
28
+
29
+ // Register a metric-matched fallback so the placeholder dots occupy this
30
+ // font's box (no reflow on swap-in, matched dots for missing glyphs).
31
+ if (verticalMetrics) ensureMetricFallback(fontFamily, verticalMetrics);
27
32
  if (isFontLoaded(fontFamily)) {
28
33
  setLoaded(true);
29
34
  return;
@@ -34,18 +39,23 @@ const useFontStyle = _ref => {
34
39
  if (mounted.current) setLoaded(true);
35
40
  }).catch(console.error);
36
41
  }, [fontFamily]);
42
+
43
+ // Prefer the metric-matched fallback (when this font has metrics), with the
44
+ // generic Fallback as the ultimate catch-all.
45
+ const fallbackChain = fontFamily && verticalMetrics && verticalMetrics.unitsPerEm > 0 ? `"${metricFallbackFamily(fontFamily)}", ${FALLBACK_FAMILY}` : FALLBACK_FAMILY;
37
46
  const cssStyle = loaded && fontFamily ? {
38
- fontFamily: `"${fontFamily}", ${FALLBACK_FAMILY}`,
47
+ fontFamily: `"${fontFamily}", ${fallbackChain}`,
39
48
  fontWeight: 400,
40
49
  fontStyle: 'normal'
41
50
  } : {
42
- fontFamily: FALLBACK_FAMILY,
51
+ fontFamily: fallbackChain,
43
52
  fontWeight: 'normal',
44
53
  fontStyle: 'normal'
45
54
  };
46
55
  return {
47
56
  style: cssStyle,
48
- loaded
57
+ loaded,
58
+ verticalMetrics: verticalMetrics ?? null
49
59
  };
50
60
  };
51
61
  export default useFontStyle;
@@ -1,6 +1,2 @@
1
- export declare function setCorsModalEnabled(enabled: boolean): void;
2
- export interface CorsError {
3
- origin: string;
4
- fontdueUrl: string;
5
- }
1
+ export declare function setConnectionErrorUiEnabled(enabled: boolean): void;
6
2
  export declare function handlePossibleCorsError(error: unknown, fetchUrl: string): boolean;
package/dist/corsError.js CHANGED
@@ -1,9 +1,14 @@
1
1
  import retryImport from './retryImport.js';
2
2
  let detected = false;
3
3
  let navigating = false;
4
- let modalEnabled = true;
5
- export function setCorsModalEnabled(enabled) {
6
- modalEnabled = enabled;
4
+ let connectionErrorUiEnabled = true;
5
+
6
+ // Gate for the visible connection-error UI (the admin toolbar's status). Wired
7
+ // from the `corsErrorModal` config option (legacy name, kept for
8
+ // compatibility). When disabled we still log and keep polling for recovery — we
9
+ // just never publish a status for the toolbar to surface.
10
+ export function setConnectionErrorUiEnabled(enabled) {
11
+ connectionErrorUiEnabled = enabled;
7
12
  }
8
13
  if (typeof window !== 'undefined') {
9
14
  window.addEventListener('beforeunload', () => {
@@ -45,17 +50,22 @@ async function isCorsBlocked(fetchUrl) {
45
50
  return false; // server unreachable, network error
46
51
  }
47
52
  }
48
- function showCorsError(origin, fetchUrl) {
49
- console.error(`[Fontdue] Cross-origin request to ${fetchUrl} was blocked.\n\n` + `Your website (${origin}) is not listed as an allowed origin ` + `in your Fontdue CORS settings.\n\n` + `To fix this:\n` + `1. Log in to your Fontdue dashboard\n` + `2. Go to Settings \u2192 Security\n` + `3. Add "${origin}" to the "Cross-origin API access" field\n` + `4. Save \u2014 this page will reload automatically`);
50
- if (modalEnabled) {
51
- retryImport(() => import('./components/CorsErrorModal.js')).then(_ref => {
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).`);
55
+
56
+ // Surface the connection status as a compact, toolbar-styled widget (red
57
+ // button + alert glyph) rather than the old full-screen modal. It mounts in
58
+ // its own React root — see ConnectionErrorToolbar for why it isn't part of
59
+ // the in-tree admin toolbar.
60
+ if (connectionErrorUiEnabled) {
61
+ retryImport(() => import('./components/ConnectionErrorToolbar.js')).then(_ref => {
52
62
  let {
53
- renderCorsErrorModal
63
+ renderConnectionErrorToolbar
54
64
  } = _ref;
55
- renderCorsErrorModal(origin, fetchUrl);
65
+ renderConnectionErrorToolbar(origin, fetchUrl);
56
66
  }).catch(() => {
57
- // Chunk failed to load — the console.error above already
58
- // told the developer what's wrong.
67
+ // Chunk failed to load — the console.error above already told the
68
+ // developer what's wrong.
59
69
  });
60
70
  }
61
71
  startPolling(fetchUrl);
@@ -72,10 +82,10 @@ export function handlePossibleCorsError(error, fetchUrl) {
72
82
  // Verify this is actually a CORS error and not a network failure.
73
83
  // We do this async — handlePossibleCorsError still returns true
74
84
  // synchronously so the caller can return a stub response instead
75
- // of throwing, but we only show the modal if CORS is confirmed.
85
+ // of throwing, but we only surface a status if CORS is confirmed.
76
86
  isCorsBlocked(fetchUrl).then(blocked => {
77
87
  if (blocked) {
78
- showCorsError(origin, fetchUrl);
88
+ publishConnectionError(origin, fetchUrl);
79
89
  } else {
80
90
  // Network error, not CORS — reset so a real CORS error
81
91
  // can still be detected if connectivity recovers.
@@ -0,0 +1,2 @@
1
+ export declare const FONTDUE_CDN_URL = "https://cdn.fontdue.com";
2
+ export declare function unicodeNamesUrl(cdnUrl?: string): string;
@@ -0,0 +1,18 @@
1
+ import { UNICODE_NAMES_VERSION } from './unicodeNamesVersion.js';
2
+
3
+ // The unicode code-point→name map (~1.2 MB) is not bundled — it's served as a
4
+ // static, immutable JSON asset from the Fontdue CDN and fetched at runtime by
5
+ // CharacterViewer. The filename is content-hashed (UNICODE_NAMES_VERSION) so it
6
+ // can be cached forever; a data change produces a new filename.
7
+ //
8
+ // The asset is uploaded to the CDN under `/js/dist/` by the data-refresh flow
9
+ // (`npm run update-unicode-data`, which also runs `npm run publish-unicode-data`)
10
+ // — not by the npm publish or the app deploy. See fontdue-js/src/scripts.
11
+ //
12
+ // `/js/dist/` is the path every Fontdue CDN asset is served from on the cdn.*
13
+ // hosts (the bucket's `js/` prefix served at root); it is NOT a bare `/dist/`.
14
+ export const FONTDUE_CDN_URL = 'https://cdn.fontdue.com';
15
+ export function unicodeNamesUrl() {
16
+ let cdnUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : FONTDUE_CDN_URL;
17
+ return `${cdnUrl.replace(/\/+$/, '')}/js/dist/unicode-names.${UNICODE_NAMES_VERSION}.json`;
18
+ }
@@ -0,0 +1 @@
1
+ export declare const UNICODE_NAMES_VERSION = "3132bac0592155bd";
@@ -0,0 +1,4 @@
1
+ // Generated by `npm run update-unicode-data`. Content hash of unicodeNames.json.
2
+ // Both the CDN asset filename and the runtime fetch URL derive from this, so
3
+ // they always agree. Do not edit by hand.
4
+ export const UNICODE_NAMES_VERSION = "3132bac0592155bd";
@@ -0,0 +1,2 @@
1
+ export declare const FALLBACK_FONT_WOFF2_BASE64 = "d09GMgABAAAAAADYAAoAAAAAAfAAAACQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk4AKAoYNwsGAAE2AiQDBgQgBQYHIBtSAciuDmzHfIwcg7zyGj36BfE8dGnv70xqs5AX4ZCucZY0yTXRgFyjzRELyQwA4SlI9ET4Ii+c/07ghH6YVABETR5hoAGnbXd2cSn13Et3RDHF3AIcAQHrPgIaUBFUDAEVkHqdmd4ObdsCQfj6X6u/lfICANClyCgaEMqrR546XQgAIHmiEzSqgAYAAAwVAAAA";
2
+ export declare const FALLBACK_ADVANCE_PER_EM = 0.458;
@@ -0,0 +1,10 @@
1
+ // Generated by scripts/generate_fallback_font.py — do not edit by hand.
2
+ //
3
+ // The universal minimal fallback font (renders a dot for every Unicode
4
+ // codepoint), embedded as base64 so per-family metric-matched fallbacks can
5
+ // be built at runtime via the FontFace API (see metricFallback.ts).
6
+ export const FALLBACK_FONT_WOFF2_BASE64 = 'd09GMgABAAAAAADYAAoAAAAAAfAAAACQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk4AKAoYNwsGAAE2AiQDBgQgBQYHIBtSAciuDmzHfIwcg7zyGj36BfE8dGnv70xqs5AX4ZCucZY0yTXRgFyjzRELyQwA4SlI9ET4Ii+c/07ghH6YVABETR5hoAGnbXd2cSn13Et3RDHF3AIcAQHrPgIaUBFUDAEVkHqdmd4ObdsCQfj6X6u/lfICANClyCgaEMqrR546XQgAIHmiEzSqgAYAAAwVAAAA';
7
+
8
+ // The dot font's intrinsic average advance, in em — every glyph advances
9
+ // 458/1000 em. Used as the denominator when computing `size-adjust`.
10
+ export const FALLBACK_ADVANCE_PER_EM = 0.458;