@newtonedev/components 0.1.7 → 0.1.8

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 (94) hide show
  1. package/dist/composites/actions/Button/Button.d.ts.map +1 -1
  2. package/dist/composites/form-controls/Select/Select.styles.d.ts.map +1 -1
  3. package/dist/composites/form-controls/TextInput/TextInput.styles.d.ts.map +1 -1
  4. package/dist/composites/form-controls/Toggle/Toggle.styles.d.ts.map +1 -1
  5. package/dist/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.d.ts.map +1 -1
  6. package/dist/composites/range-inputs/HueSlider/HueSlider.styles.d.ts.map +1 -1
  7. package/dist/composites/range-inputs/Slider/Slider.styles.d.ts.map +1 -1
  8. package/dist/fonts/GoogleFontLoader.d.ts +5 -4
  9. package/dist/fonts/GoogleFontLoader.d.ts.map +1 -1
  10. package/dist/fonts/SelfHostedFontLoader.d.ts +14 -0
  11. package/dist/fonts/SelfHostedFontLoader.d.ts.map +1 -0
  12. package/dist/fonts/buildGoogleFontsUrl.d.ts +1 -16
  13. package/dist/fonts/buildGoogleFontsUrl.d.ts.map +1 -1
  14. package/dist/fonts/measureFont.d.ts +18 -0
  15. package/dist/fonts/measureFont.d.ts.map +1 -0
  16. package/dist/fonts/reportQueue.d.ts +7 -0
  17. package/dist/fonts/reportQueue.d.ts.map +1 -0
  18. package/dist/fonts/useLocalCalibration.d.ts +19 -0
  19. package/dist/fonts/useLocalCalibration.d.ts.map +1 -0
  20. package/dist/fonts/useTypographyCalibrations.d.ts +11 -0
  21. package/dist/fonts/useTypographyCalibrations.d.ts.map +1 -0
  22. package/dist/index.cjs +628 -422
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts +7 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +567 -376
  27. package/dist/index.js.map +1 -1
  28. package/dist/primitives/Icon/Icon.types.d.ts +1 -1
  29. package/dist/primitives/Icon/Icon.types.d.ts.map +1 -1
  30. package/dist/primitives/Text/Text.d.ts +33 -8
  31. package/dist/primitives/Text/Text.d.ts.map +1 -1
  32. package/dist/primitives/Text/Text.spans.d.ts +22 -0
  33. package/dist/primitives/Text/Text.spans.d.ts.map +1 -0
  34. package/dist/primitives/Text/Text.types.d.ts +75 -27
  35. package/dist/primitives/Text/Text.types.d.ts.map +1 -1
  36. package/dist/primitives/Text/index.d.ts +23 -2
  37. package/dist/primitives/Text/index.d.ts.map +1 -1
  38. package/dist/primitives/index.d.ts +1 -1
  39. package/dist/primitives/index.d.ts.map +1 -1
  40. package/dist/registry/codegen.d.ts.map +1 -1
  41. package/dist/registry/registry.d.ts.map +1 -1
  42. package/dist/registry/types.d.ts +2 -0
  43. package/dist/registry/types.d.ts.map +1 -1
  44. package/dist/theme/NewtoneProvider.d.ts +9 -1
  45. package/dist/theme/NewtoneProvider.d.ts.map +1 -1
  46. package/dist/theme/defaults.d.ts +1 -0
  47. package/dist/theme/defaults.d.ts.map +1 -1
  48. package/dist/theme/types.d.ts +48 -32
  49. package/dist/theme/types.d.ts.map +1 -1
  50. package/dist/theme/useBreakpoint.d.ts +9 -0
  51. package/dist/theme/useBreakpoint.d.ts.map +1 -0
  52. package/dist/tokens/computeTokens.d.ts +9 -22
  53. package/dist/tokens/computeTokens.d.ts.map +1 -1
  54. package/dist/tokens/types.d.ts +40 -22
  55. package/dist/tokens/types.d.ts.map +1 -1
  56. package/package.json +2 -1
  57. package/src/composites/actions/Button/Button.styles.ts +3 -3
  58. package/src/composites/actions/Button/Button.tsx +3 -2
  59. package/src/composites/form-controls/Select/Select.styles.ts +8 -8
  60. package/src/composites/form-controls/Select/Select.tsx +1 -1
  61. package/src/composites/form-controls/Select/SelectOption.tsx +3 -3
  62. package/src/composites/form-controls/TextInput/TextInput.styles.ts +5 -5
  63. package/src/composites/form-controls/Toggle/Toggle.styles.ts +3 -3
  64. package/src/composites/range-inputs/ColorScaleSlider/ColorScaleSlider.styles.ts +6 -6
  65. package/src/composites/range-inputs/HueSlider/HueSlider.styles.ts +9 -9
  66. package/src/composites/range-inputs/Slider/Slider.styles.ts +9 -9
  67. package/src/fonts/GoogleFontLoader.tsx +25 -10
  68. package/src/fonts/SelfHostedFontLoader.tsx +44 -0
  69. package/src/fonts/buildGoogleFontsUrl.ts +2 -31
  70. package/src/fonts/measureFont.ts +42 -0
  71. package/src/fonts/reportQueue.ts +54 -0
  72. package/src/fonts/useLocalCalibration.ts +97 -0
  73. package/src/fonts/useTypographyCalibrations.ts +15 -0
  74. package/src/index.ts +16 -7
  75. package/src/primitives/Frame/Frame.tsx +3 -3
  76. package/src/primitives/Icon/Icon.tsx +1 -1
  77. package/src/primitives/Icon/Icon.types.ts +1 -1
  78. package/src/primitives/Text/Text.spans.ts +57 -0
  79. package/src/primitives/Text/Text.tsx +205 -53
  80. package/src/primitives/Text/Text.types.ts +80 -27
  81. package/src/primitives/Text/index.ts +27 -3
  82. package/src/primitives/index.ts +3 -2
  83. package/src/registry/codegen.ts +1 -0
  84. package/src/registry/registry.ts +55 -53
  85. package/src/registry/types.ts +2 -0
  86. package/src/theme/NewtoneProvider.tsx +18 -2
  87. package/src/theme/defaults.ts +8 -28
  88. package/src/theme/types.ts +63 -33
  89. package/src/theme/useBreakpoint.ts +14 -0
  90. package/src/tokens/computeTokens.ts +23 -19
  91. package/src/tokens/types.ts +10 -24
  92. package/dist/fonts/googleFonts.d.ts +0 -20
  93. package/dist/fonts/googleFonts.d.ts.map +0 -1
  94. package/src/fonts/googleFonts.ts +0 -87
@@ -106,32 +106,50 @@ export interface ResolvedTokens {
106
106
  readonly xl: number;
107
107
  readonly pill: 999;
108
108
  };
109
- /** Typography tokens for fonts, sizes, line heights, and weights */
109
+ /** Typography tokens for fonts (per-scope) and primitive size/lineHeight scales */
110
110
  readonly typography: {
111
+ /** Per-scope font family + weight mapping */
111
112
  readonly fonts: {
112
- readonly mono: string;
113
- readonly display: string;
114
- readonly default: string;
113
+ readonly main: {
114
+ readonly family: string;
115
+ readonly weights: {
116
+ readonly regular: number;
117
+ readonly medium: number;
118
+ readonly bold: number;
119
+ };
120
+ };
121
+ readonly display: {
122
+ readonly family: string;
123
+ readonly weights: {
124
+ readonly regular: number;
125
+ readonly medium: number;
126
+ readonly bold: number;
127
+ };
128
+ };
129
+ readonly mono: {
130
+ readonly family: string;
131
+ readonly weights: {
132
+ readonly regular: number;
133
+ readonly medium: number;
134
+ readonly bold: number;
135
+ };
136
+ };
137
+ readonly currency: {
138
+ readonly family: string;
139
+ readonly weights: {
140
+ readonly regular: number;
141
+ readonly medium: number;
142
+ readonly bold: number;
143
+ };
144
+ };
115
145
  };
116
- readonly size: {
117
- readonly xs: number;
118
- readonly sm: number;
119
- readonly base: number;
120
- readonly md: number;
121
- readonly lg: number;
122
- readonly xl: number;
123
- readonly xxl: number;
146
+ /** Primitive font size scale — numbered tokens ('01'–'16'), values in px */
147
+ readonly fontSizes: {
148
+ readonly [key: string]: number;
124
149
  };
125
- readonly lineHeight: {
126
- readonly tight: number;
127
- readonly normal: number;
128
- readonly relaxed: number;
129
- };
130
- readonly weight: {
131
- readonly regular: number;
132
- readonly medium: number;
133
- readonly semibold: number;
134
- readonly bold: number;
150
+ /** Primitive line height scale — numbered tokens ('01'–'16'), values in px */
151
+ readonly lineHeights: {
152
+ readonly [key: string]: number;
135
153
  };
136
154
  };
137
155
  /** Icon tokens for Material Symbols configuration */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tokens/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tokens/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newtonedev/components",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "React + React Native Web component library for Newtone",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,6 +37,7 @@
37
37
  "react-native": ">=0.70.0"
38
38
  },
39
39
  "dependencies": {
40
+ "@newtonedev/fonts": "file:../../../newtone-fonts",
40
41
  "newtone": "^0.1.0",
41
42
  "react-native-web": "^0.19.10"
42
43
  },
@@ -184,21 +184,21 @@ function getSizeConfig(size: ButtonSize, tokens: UseTokensResult) {
184
184
  padding: 8,
185
185
  gap: tokens.spacing['08'],
186
186
  borderRadius: 8,
187
- textSize: 'base', // 16px
187
+ textSize: 'md', // 16px
188
188
  iconSize: 24,
189
189
  },
190
190
  md: {
191
191
  padding: 12,
192
192
  gap: tokens.spacing['08'],
193
193
  borderRadius: 12,
194
- textSize: 'base', // 16px
194
+ textSize: 'md', // 16px
195
195
  iconSize: 24,
196
196
  },
197
197
  lg: {
198
198
  padding: 16,
199
199
  gap: tokens.spacing['08'],
200
200
  borderRadius: 16,
201
- textSize: 'base', // 16px
201
+ textSize: 'md', // 16px
202
202
  iconSize: 24,
203
203
  },
204
204
  };
@@ -4,7 +4,7 @@ import type { ButtonProps } from './Button.types';
4
4
  import { getButtonConfig, computeButtonPadding } from './Button.styles';
5
5
  import { useTokens } from '../../../tokens/useTokens';
6
6
  import { Icon } from '../../../primitives/Icon/Icon';
7
- import { Text } from '../../../primitives/Text/Text';
7
+ import { Text } from '../../../primitives/Text';
8
8
  import { Wrapper } from '../../../primitives/Wrapper/Wrapper';
9
9
 
10
10
  /**
@@ -99,8 +99,9 @@ export function Button({
99
99
  {children && (
100
100
  // Text primitive with semantic props + color style override
101
101
  <Text
102
+ role="label"
102
103
  size={sizeTokens.textSize}
103
- weight="semibold"
104
+ centerVertically
104
105
  style={[
105
106
  { color: variantColors.textColor },
106
107
  ...(Array.isArray(textStyle) ? textStyle : textStyle ? [textStyle] : []),
@@ -9,7 +9,7 @@ export function getSelectStyles(
9
9
  isOpen: boolean
10
10
  ) {
11
11
  const isSm = size === 'sm';
12
- const fontSize = isSm ? tokens.typography.size.sm : tokens.typography.size.base;
12
+ const fontSize = isSm ? tokens.typography.fontSizes['04'] : tokens.typography.fontSizes['05'];
13
13
  const iconSize = fontSize + 2;
14
14
  const iconSpace = iconSize + tokens.spacing['08'];
15
15
  const paddingVertical = isSm ? tokens.spacing['04'] : tokens.spacing['08'];
@@ -21,9 +21,9 @@ export function getSelectStyles(
21
21
  zIndex: isOpen ? 999 : 0,
22
22
  },
23
23
  label: {
24
- fontFamily: tokens.typography.fonts.default,
25
- fontSize: tokens.typography.size.sm,
26
- fontWeight: tokens.typography.weight.semibold as any,
24
+ fontFamily: tokens.typography.fonts.main.family,
25
+ fontSize: tokens.typography.fontSizes['04'],
26
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
27
27
  color: srgbToHex(tokens.textSecondary.srgb),
28
28
  },
29
29
  trigger: {
@@ -40,7 +40,7 @@ export function getSelectStyles(
40
40
  },
41
41
  triggerText: {
42
42
  flex: 1,
43
- fontFamily: tokens.typography.fonts.default,
43
+ fontFamily: tokens.typography.fonts.main.family,
44
44
  fontSize,
45
45
  color: disabled
46
46
  ? srgbToHex(tokens.textSecondary.srgb)
@@ -54,9 +54,9 @@ export function getSelectStyles(
54
54
  justifyContent: 'center',
55
55
  },
56
56
  groupLabel: {
57
- fontFamily: tokens.typography.fonts.default,
58
- fontSize: tokens.typography.size.xs,
59
- fontWeight: tokens.typography.weight.semibold as any,
57
+ fontFamily: tokens.typography.fonts.main.family,
58
+ fontSize: tokens.typography.fontSizes['01'],
59
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
60
60
  color: srgbToHex(tokens.textSecondary.srgb),
61
61
  textTransform: 'uppercase',
62
62
  letterSpacing: 0.5,
@@ -86,7 +86,7 @@ export function Select({
86
86
  <View style={styles.iconWrapper} pointerEvents="none">
87
87
  <Icon
88
88
  name={isOpen ? 'expand_less' : 'expand_more'}
89
- size={size === 'sm' ? tokens.typography.size.sm + 2 : tokens.typography.size.base + 2}
89
+ size={size === 'sm' ? tokens.typography.fontSizes['04'] + 2 : tokens.typography.fontSizes['05'] + 2}
90
90
  color={iconColor}
91
91
  />
92
92
  </View>
@@ -26,7 +26,7 @@ export function SelectOptionRow({
26
26
 
27
27
  const paddingVertical = size === 'sm' ? tokens.spacing['04'] : tokens.spacing['08'];
28
28
  const paddingHorizontal = size === 'sm' ? tokens.spacing['08'] : tokens.spacing['12'];
29
- const fontSize = size === 'sm' ? tokens.typography.size.sm : tokens.typography.size.base;
29
+ const fontSize = size === 'sm' ? tokens.typography.fontSizes['04'] : tokens.typography.fontSizes['05'];
30
30
 
31
31
  if (renderOption) {
32
32
  return (
@@ -74,12 +74,12 @@ export function SelectOptionRow({
74
74
  style={[
75
75
  {
76
76
  flex: 1,
77
- fontFamily: tokens.typography.fonts.default,
77
+ fontFamily: tokens.typography.fonts.main.family,
78
78
  fontSize,
79
79
  color: srgbToHex(tokens.textPrimary.srgb),
80
80
  },
81
81
  isSelected && {
82
- fontWeight: tokens.typography.weight.semibold as any,
82
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
83
83
  color: srgbToHex(tokens.accent.fill.srgb),
84
84
  },
85
85
  option.disabled && {
@@ -8,20 +8,20 @@ export function getTextInputStyles(tokens: ResolvedTokens, disabled: boolean) {
8
8
  gap: tokens.spacing['04'],
9
9
  },
10
10
  label: {
11
- fontFamily: tokens.typography.fonts.default,
12
- fontSize: tokens.typography.size.sm,
13
- fontWeight: tokens.typography.weight.semibold as any,
11
+ fontFamily: tokens.typography.fonts.main.family,
12
+ fontSize: tokens.typography.fontSizes['04'],
13
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
14
14
  color: srgbToHex(tokens.textSecondary.srgb),
15
15
  },
16
16
  input: {
17
- fontFamily: tokens.typography.fonts.default,
17
+ fontFamily: tokens.typography.fonts.main.family,
18
18
  backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
19
19
  borderWidth: 1,
20
20
  borderColor: srgbToHex(tokens.border.srgb),
21
21
  borderRadius: tokens.radius.md,
22
22
  paddingVertical: tokens.spacing['08'],
23
23
  paddingHorizontal: tokens.spacing['12'],
24
- fontSize: tokens.typography.size.base,
24
+ fontSize: tokens.typography.fontSizes['05'],
25
25
  color: disabled
26
26
  ? srgbToHex(tokens.textSecondary.srgb)
27
27
  : srgbToHex(tokens.textPrimary.srgb),
@@ -20,9 +20,9 @@ export function getToggleStyles(
20
20
  opacity: disabled ? 0.5 : 1,
21
21
  },
22
22
  label: {
23
- fontFamily: tokens.typography.fonts.default,
24
- fontSize: tokens.typography.size.sm,
25
- fontWeight: tokens.typography.weight.semibold as any,
23
+ fontFamily: tokens.typography.fonts.main.family,
24
+ fontSize: tokens.typography.fontSizes['04'],
25
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
26
26
  color: srgbToHex(tokens.textSecondary.srgb),
27
27
  },
28
28
  track: {
@@ -17,9 +17,9 @@ export function getColorScaleSliderStyles(tokens: ResolvedTokens, disabled: bool
17
17
  alignItems: 'center',
18
18
  },
19
19
  label: {
20
- fontFamily: tokens.typography.fonts.default,
21
- fontSize: tokens.typography.size.sm,
22
- fontWeight: tokens.typography.weight.semibold as any,
20
+ fontFamily: tokens.typography.fonts.main.family,
21
+ fontSize: tokens.typography.fontSizes['04'],
22
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
23
23
  color: srgbToHex(tokens.textSecondary.srgb),
24
24
  },
25
25
  trackContainer: {
@@ -49,9 +49,9 @@ export function getColorScaleSliderStyles(tokens: ResolvedTokens, disabled: bool
49
49
  borderColor: srgbToHex(tokens.border.srgb),
50
50
  },
51
51
  warning: {
52
- fontFamily: tokens.typography.fonts.default,
53
- fontSize: tokens.typography.size.xs,
54
- fontWeight: tokens.typography.weight.medium as any,
52
+ fontFamily: tokens.typography.fonts.main.family,
53
+ fontSize: tokens.typography.fontSizes['01'],
54
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
55
55
  color: srgbToHex(tokens.error.fill.srgb),
56
56
  },
57
57
  });
@@ -41,15 +41,15 @@ export function getHueSliderStyles(tokens: ResolvedTokens, disabled: boolean) {
41
41
  alignItems: 'center',
42
42
  },
43
43
  label: {
44
- fontFamily: tokens.typography.fonts.default,
45
- fontSize: tokens.typography.size.sm,
46
- fontWeight: tokens.typography.weight.semibold as any,
44
+ fontFamily: tokens.typography.fonts.main.family,
45
+ fontSize: tokens.typography.fontSizes['04'],
46
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
47
47
  color: srgbToHex(tokens.textSecondary.srgb),
48
48
  },
49
49
  value: {
50
- fontFamily: tokens.typography.fonts.default,
51
- fontSize: tokens.typography.size.sm,
52
- fontWeight: tokens.typography.weight.medium as any,
50
+ fontFamily: tokens.typography.fonts.main.family,
51
+ fontSize: tokens.typography.fontSizes['04'],
52
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
53
53
  color: srgbToHex(tokens.textPrimary.srgb),
54
54
  },
55
55
  valueInput: {
@@ -61,9 +61,9 @@ export function getHueSliderStyles(tokens: ResolvedTokens, disabled: boolean) {
61
61
  borderRadius: 4,
62
62
  backgroundColor: 'transparent',
63
63
  color: srgbToHex(tokens.textPrimary.srgb),
64
- fontFamily: tokens.typography.fonts.default,
65
- fontSize: tokens.typography.size.sm,
66
- fontWeight: tokens.typography.weight.medium as any,
64
+ fontFamily: tokens.typography.fonts.main.family,
65
+ fontSize: tokens.typography.fontSizes['04'],
66
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
67
67
  textAlign: 'right',
68
68
  },
69
69
  trackContainer: {
@@ -17,15 +17,15 @@ export function getSliderStyles(tokens: ResolvedTokens, disabled: boolean) {
17
17
  alignItems: 'center',
18
18
  },
19
19
  label: {
20
- fontFamily: tokens.typography.fonts.default,
21
- fontSize: tokens.typography.size.sm,
22
- fontWeight: tokens.typography.weight.semibold as any,
20
+ fontFamily: tokens.typography.fonts.main.family,
21
+ fontSize: tokens.typography.fontSizes['04'],
22
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
23
23
  color: srgbToHex(tokens.textSecondary.srgb),
24
24
  },
25
25
  value: {
26
- fontFamily: tokens.typography.fonts.default,
27
- fontSize: tokens.typography.size.sm,
28
- fontWeight: tokens.typography.weight.medium as any,
26
+ fontFamily: tokens.typography.fonts.main.family,
27
+ fontSize: tokens.typography.fontSizes['04'],
28
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
29
29
  color: srgbToHex(tokens.textPrimary.srgb),
30
30
  },
31
31
  valueInput: {
@@ -37,9 +37,9 @@ export function getSliderStyles(tokens: ResolvedTokens, disabled: boolean) {
37
37
  borderRadius: 4,
38
38
  backgroundColor: 'transparent',
39
39
  color: srgbToHex(tokens.textPrimary.srgb),
40
- fontFamily: tokens.typography.fonts.default,
41
- fontSize: tokens.typography.size.sm,
42
- fontWeight: tokens.typography.weight.medium as any,
40
+ fontFamily: tokens.typography.fonts.main.family,
41
+ fontSize: tokens.typography.fontSizes['04'],
42
+ fontWeight: tokens.typography.fonts.main.weights.medium as any,
43
43
  textAlign: 'right',
44
44
  },
45
45
  trackContainer: {
@@ -1,12 +1,13 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import type { FontConfig } from '../theme/types';
2
+ import type { FontSlot } from '../theme/types';
3
3
  import { buildGoogleFontsUrl } from './buildGoogleFontsUrl';
4
4
 
5
5
  interface GoogleFontLoaderProps {
6
6
  readonly fonts: {
7
- readonly mono: FontConfig;
8
- readonly display: FontConfig;
9
- readonly default: FontConfig;
7
+ readonly main: FontSlot;
8
+ readonly display: FontSlot;
9
+ readonly mono: FontSlot;
10
+ readonly currency: FontSlot;
10
11
  };
11
12
  }
12
13
 
@@ -53,12 +54,26 @@ export function GoogleFontLoader({ fonts }: GoogleFontLoaderProps) {
53
54
  }
54
55
  };
55
56
  }, [
56
- fonts.mono.family,
57
- fonts.mono.type,
58
- fonts.display.family,
59
- fonts.display.type,
60
- fonts.default.family,
61
- fonts.default.type,
57
+ fonts.main.config.family,
58
+ fonts.main.config.type,
59
+ fonts.main.weights.regular,
60
+ fonts.main.weights.medium,
61
+ fonts.main.weights.bold,
62
+ fonts.display.config.family,
63
+ fonts.display.config.type,
64
+ fonts.display.weights.regular,
65
+ fonts.display.weights.medium,
66
+ fonts.display.weights.bold,
67
+ fonts.mono.config.family,
68
+ fonts.mono.config.type,
69
+ fonts.mono.weights.regular,
70
+ fonts.mono.weights.medium,
71
+ fonts.mono.weights.bold,
72
+ fonts.currency.config.family,
73
+ fonts.currency.config.type,
74
+ fonts.currency.weights.regular,
75
+ fonts.currency.weights.medium,
76
+ fonts.currency.weights.bold,
62
77
  ]);
63
78
 
64
79
  return null;
@@ -0,0 +1,44 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ interface SelfHostedFontLoaderProps {
4
+ /** Raw @font-face CSS string from the theme API. */
5
+ readonly fontFaceCss: string;
6
+ }
7
+
8
+ /**
9
+ * Injects self-hosted @font-face CSS into <head> via a <style> tag.
10
+ * Used instead of GoogleFontLoader when self-hosted font CSS is available.
11
+ *
12
+ * Uses imperative DOM manipulation because react-native-web's View
13
+ * cannot render <style> elements.
14
+ */
15
+ export function SelfHostedFontLoader({ fontFaceCss }: SelfHostedFontLoaderProps) {
16
+ const styleRef = useRef<HTMLStyleElement | null>(null);
17
+
18
+ useEffect(() => {
19
+ if (typeof document === 'undefined') return;
20
+
21
+ // Clean up previous style
22
+ if (styleRef.current) {
23
+ styleRef.current.remove();
24
+ styleRef.current = null;
25
+ }
26
+
27
+ if (!fontFaceCss) return;
28
+
29
+ const style = document.createElement('style');
30
+ style.setAttribute('data-newtone-fonts', 'self-hosted');
31
+ style.textContent = fontFaceCss;
32
+ document.head.appendChild(style);
33
+ styleRef.current = style;
34
+
35
+ return () => {
36
+ if (styleRef.current) {
37
+ styleRef.current.remove();
38
+ styleRef.current = null;
39
+ }
40
+ };
41
+ }, [fontFaceCss]);
42
+
43
+ return null;
44
+ }
@@ -1,31 +1,2 @@
1
- import type { FontConfig } from '../theme/types';
2
-
3
- /**
4
- * Build a Google Fonts CSS API v2 URL for all google-type fonts in the config.
5
- * Returns null if no Google fonts are present.
6
- *
7
- * @example
8
- * ```
9
- * buildGoogleFontsUrl(fonts)
10
- * // => "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500;600;700&display=swap"
11
- * ```
12
- */
13
- export function buildGoogleFontsUrl(
14
- fonts: { readonly mono: FontConfig; readonly display: FontConfig; readonly default: FontConfig },
15
- ): string | null {
16
- const googleFonts = [fonts.mono, fonts.display, fonts.default].filter(
17
- (f) => f.type === 'google',
18
- );
19
-
20
- if (googleFonts.length === 0) return null;
21
-
22
- // Deduplicate by family name
23
- const unique = [...new Map(googleFonts.map((f) => [f.family, f])).values()];
24
-
25
- const families = unique.map((f) => {
26
- const encoded = f.family.replace(/ /g, '+');
27
- return `family=${encoded}:wght@400;500;600;700`;
28
- });
29
-
30
- return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`;
31
- }
1
+ // Re-export from @newtonedev/fonts (canonical source)
2
+ export { buildGoogleFontsUrl } from '@newtonedev/fonts';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Reference string for character width measurement.
3
+ * Covers uppercase, lowercase, digits, and a space — 63 characters total.
4
+ */
5
+ const REF_STRING =
6
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ';
7
+
8
+ /**
9
+ * Measure the average character width ratio for a font using the Canvas API.
10
+ *
11
+ * Waits for the font to load via `document.fonts.load()` before measuring.
12
+ * Falls back to 0.55 if the browser context is unavailable, the font fails
13
+ * to load, or canvas is not supported.
14
+ *
15
+ * The returned ratio represents `avgCharWidth / fontSize`. Multiply by a
16
+ * desired fontSize to estimate character widths for that font.
17
+ *
18
+ * @param fontFamily - Font family name, e.g. "Inter" or "system-ui"
19
+ * @param fontWeight - CSS font-weight number, e.g. 400
20
+ * @param fallback - CSS fallback stack, e.g. "sans-serif"
21
+ * @param fontSize - Reference font size in px (default 16)
22
+ * @returns Average character width ratio, e.g. 0.52 for Inter
23
+ */
24
+ export async function measureAvgCharWidth(
25
+ fontFamily: string,
26
+ fontWeight: number,
27
+ fallback: string,
28
+ fontSize = 16,
29
+ ): Promise<number> {
30
+ if (typeof document === 'undefined') return 0.55;
31
+ try {
32
+ await document.fonts.load(`${fontWeight} ${fontSize}px "${fontFamily}"`);
33
+ const canvas = document.createElement('canvas');
34
+ const ctx = canvas.getContext('2d');
35
+ if (!ctx) return 0.55;
36
+ ctx.font = `${fontWeight} ${fontSize}px "${fontFamily}", ${fallback}`;
37
+ const ratio = ctx.measureText(REF_STRING).width / REF_STRING.length / fontSize;
38
+ return Math.round(ratio * 1000) / 1000;
39
+ } catch {
40
+ return 0.55;
41
+ }
42
+ }
@@ -0,0 +1,54 @@
1
+ import type { ObserverPayload } from '@newtonedev/fonts';
2
+
3
+ /**
4
+ * Module-level batch queue for typography observations (Layer 3 reporting).
5
+ *
6
+ * Lives outside React's component lifecycle so it survives re-renders.
7
+ * Observations are enqueued immediately and flushed in a single batch after
8
+ * a 2s debounce — minimising network requests and allowing coalescence.
9
+ *
10
+ * Usage: call `enqueueObservation(endpoint, payload)` from responsive Text
11
+ * instances. Set `reportingEndpoint` on `NewtoneProvider` to opt in.
12
+ */
13
+
14
+ interface QueuedObservation {
15
+ readonly endpoint: string;
16
+ readonly payload: ObserverPayload;
17
+ }
18
+
19
+ const queue: QueuedObservation[] = [];
20
+ let flushTimer: ReturnType<typeof setTimeout> | undefined;
21
+
22
+ function flush(): void {
23
+ if (queue.length === 0) return;
24
+
25
+ // Group by endpoint so a page with multiple endpoints gets one request each
26
+ const byEndpoint = new Map<string, ObserverPayload[]>();
27
+ for (const item of queue) {
28
+ const group = byEndpoint.get(item.endpoint) ?? [];
29
+ group.push(item.payload);
30
+ byEndpoint.set(item.endpoint, group);
31
+ }
32
+ queue.length = 0;
33
+
34
+ for (const [endpoint, observations] of byEndpoint) {
35
+ // Fire-and-forget — reporting failures are silent and never affect the UI
36
+ fetch(endpoint, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ observations }),
40
+ // keepalive: true allows the request to outlive the page
41
+ keepalive: true,
42
+ }).catch(() => {});
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Enqueue a typography observation for batch reporting.
48
+ * Resets the 2s debounce timer on each call.
49
+ */
50
+ export function enqueueObservation(endpoint: string, payload: ObserverPayload): void {
51
+ queue.push({ endpoint, payload });
52
+ if (flushTimer !== undefined) clearTimeout(flushTimer);
53
+ flushTimer = setTimeout(flush, 2000);
54
+ }