@developer_tribe/react-builder 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/RenderPage.d.ts +3 -1
  2. package/dist/build-components/PaywallOptions/PaywallOptionButton.d.ts +1 -1
  3. package/dist/components/BuilderProvider.d.ts +17 -0
  4. package/dist/components/Checkbox.d.ts +1 -1
  5. package/dist/hooks/useExtractTextStyle.d.ts +1 -7
  6. package/dist/hooks/useExtractViewStyle.d.ts +1 -7
  7. package/dist/hooks/useLocalize.d.ts +4 -1
  8. package/dist/hooks/useSafeAreaViewStyle.d.ts +2 -1
  9. package/dist/index.cjs.js +5 -5
  10. package/dist/index.cjs.js.map +1 -1
  11. package/dist/index.d.ts +0 -6
  12. package/dist/index.esm.js +2 -2
  13. package/dist/index.esm.js.map +1 -1
  14. package/dist/index.native.cjs.js +1 -28
  15. package/dist/index.native.cjs.js.map +1 -1
  16. package/dist/index.native.esm.js +1 -28
  17. package/dist/index.native.esm.js.map +1 -1
  18. package/dist/types/Node.d.ts +1 -0
  19. package/dist/utils/extractTextStyle/extractTextStyle.d.ts +4 -0
  20. package/dist/utils/extractViewStyle/extractViewStyle.d.ts +1 -2
  21. package/dist/utils/parseColor.d.ts +1 -2
  22. package/package.json +1 -1
  23. package/src/RenderPage.tsx +32 -20
  24. package/src/build-components/BIcon/BIcon.tsx +8 -14
  25. package/src/build-components/BackgroundImage/BackgroundImage.tsx +9 -13
  26. package/src/build-components/Button/Button.tsx +11 -20
  27. package/src/build-components/Carousel/Carousel.tsx +8 -14
  28. package/src/build-components/CarouselButtons/CarouselButtons.tsx +2 -8
  29. package/src/build-components/CarouselDots/CarouselDots.tsx +3 -10
  30. package/src/build-components/CarouselItem/CarouselItem.tsx +2 -8
  31. package/src/build-components/CarouselProvider/CarouselProvider.tsx +2 -8
  32. package/src/build-components/Image/Image.tsx +11 -13
  33. package/src/build-components/Main/Main.tsx +10 -18
  34. package/src/build-components/OnboardButton/OnboardButton.tsx +7 -15
  35. package/src/build-components/OnboardButtons/OnboardButtons.tsx +7 -14
  36. package/src/build-components/OnboardDot/OnboardDot.tsx +23 -23
  37. package/src/build-components/OnboardFooter/OnboardFooter.tsx +16 -20
  38. package/src/build-components/OnboardImage/OnboardImage.tsx +10 -12
  39. package/src/build-components/OnboardItem/OnboardItem.tsx +2 -10
  40. package/src/build-components/OnboardProvider/OnboardProvider.tsx +2 -12
  41. package/src/build-components/PaywallBackground/PaywallBackground.tsx +8 -11
  42. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +9 -16
  43. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +11 -14
  44. package/src/build-components/PaywallProvider/PaywallProvider.tsx +15 -19
  45. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +10 -16
  46. package/src/build-components/RadioButton/RadioButton.tsx +11 -20
  47. package/src/build-components/Text/Text.tsx +13 -21
  48. package/src/build-components/View/View.tsx +9 -12
  49. package/src/build-components/other.tsx +1 -1
  50. package/src/components/BuilderProvider.tsx +38 -0
  51. package/src/hooks/useExtractTextStyle.ts +12 -16
  52. package/src/hooks/useExtractViewStyle.ts +7 -16
  53. package/src/hooks/useLocalize.ts +7 -6
  54. package/src/hooks/useSafeAreaViewStyle.ts +4 -5
  55. package/src/index.ts +8 -7
  56. package/src/pages/ProjectDebug.tsx +16 -15
  57. package/src/pages/ProjectPage.tsx +17 -5
  58. package/src/types/Node.ts +1 -0
  59. package/src/utils/extractTextStyle/extractTextStyle.ts +32 -13
  60. package/src/utils/extractViewStyle/extractViewStyle.ts +2 -3
  61. package/src/utils/parseColor.ts +4 -5
@@ -9,7 +9,7 @@ export function other(type: string, node: Node): React.ReactNode {
9
9
  if (type === 'CrashComponent') {
10
10
  const details =
11
11
  node && typeof node === 'object' && !Array.isArray(node)
12
- ? (node as any).attributes
12
+ ? (node as { attributes?: unknown }).attributes
13
13
  : undefined;
14
14
  throw new Error(
15
15
  `CrashComponent: intentional crash for testing${
@@ -1,6 +1,9 @@
1
1
  import React, { createContext, useContext, useMemo } from 'react';
2
2
  import type { Product } from '../paywall/types/paywall-types';
3
3
  import type { PaywallBenefits } from '../paywall/types/benefits';
4
+ import type { AppConfig } from '../types/PreviewConfig';
5
+ import type { Fonts } from '../types/Fonts';
6
+ import type { ProjectColors } from '../types/Project';
4
7
  import { RenderErrorBoundary } from './RenderErrorBoundary';
5
8
 
6
9
  // NOTE: We keep this context intentionally tiny.
@@ -15,6 +18,20 @@ export type BuilderProviderParams = {
15
18
  benefits: PaywallBenefits;
16
19
  onPaywallClose?: () => void;
17
20
  onPaywallSubscribe?: (product?: Product) => void | boolean | Promise<boolean>;
21
+ /**
22
+ * Optional runtime config + styling inputs.
23
+ * These are intentionally passed down via BuilderProvider so `build-components`
24
+ * never need to touch `useRenderStore`.
25
+ */
26
+ appConfig?: AppConfig;
27
+ projectColors?: ProjectColors;
28
+ fonts?: Fonts;
29
+ appFont?: string | undefined;
30
+ /**
31
+ * Optional selection info (used by builder UI; ignored by host runtime).
32
+ */
33
+ previewMode?: boolean;
34
+ selectedKey?: string;
18
35
  };
19
36
 
20
37
  type BuilderProviderProps = {
@@ -42,12 +59,33 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
42
59
  typeof params?.onPaywallSubscribe === 'function'
43
60
  ? params.onPaywallSubscribe
44
61
  : undefined,
62
+ appConfig:
63
+ params?.appConfig && typeof params.appConfig === 'object'
64
+ ? (params.appConfig as AppConfig)
65
+ : undefined,
66
+ projectColors:
67
+ params?.projectColors && typeof params.projectColors === 'object'
68
+ ? (params.projectColors as ProjectColors)
69
+ : undefined,
70
+ fonts: Array.isArray(params?.fonts) ? (params.fonts as Fonts) : undefined,
71
+ appFont: params?.appFont,
72
+ previewMode: !!params?.previewMode,
73
+ selectedKey:
74
+ typeof params?.selectedKey === 'string'
75
+ ? params.selectedKey
76
+ : undefined,
45
77
  }),
46
78
  [
47
79
  params?.benefits,
48
80
  params?.products,
49
81
  params?.onPaywallClose,
50
82
  params?.onPaywallSubscribe,
83
+ params?.appConfig,
84
+ params?.projectColors,
85
+ params?.fonts,
86
+ params?.appFont,
87
+ params?.previewMode,
88
+ params?.selectedKey,
51
89
  ],
52
90
  );
53
91
 
@@ -1,30 +1,26 @@
1
1
  import { useMemo } from 'react';
2
2
  import type { NodeData } from '../types/Node';
3
3
  import type { TextPropsGenerated } from '../build-components/Text/TextProps.generated';
4
- import type { AppConfig } from '../types/PreviewConfig';
5
- import type { ProjectColors } from '../types/Project';
6
- import { useRenderStore } from '../store';
4
+ import { defaultAppConfig } from '../types/PreviewConfig';
5
+ import { useBuilderParams } from '../components/BuilderProvider';
7
6
  import { extractTextStyle } from '../utils/extractTextStyle';
8
7
 
9
- export type UseExtractTextStyleOptions = {
10
- appConfig?: AppConfig;
11
- projectColors?: ProjectColors;
12
- };
13
-
14
8
  export function useExtractTextStyle<T extends TextPropsGenerated['attributes']>(
15
9
  node: NodeData<T>,
16
- options: UseExtractTextStyleOptions = {},
17
10
  ) {
18
- const storeAppConfig = useRenderStore((s) => s.appConfig);
19
- const storeProjectColors = useRenderStore((s) => s.projectColors);
20
- const fonts = useRenderStore((s) => s.fonts);
11
+ const {
12
+ appConfig: builderAppConfig,
13
+ projectColors: builderProjectColors,
14
+ fonts: builderFonts,
15
+ } = useBuilderParams();
21
16
 
22
- const appConfig = options.appConfig ?? storeAppConfig;
23
- const projectColors = options.projectColors ?? storeProjectColors;
17
+ const appConfig = builderAppConfig ?? defaultAppConfig;
18
+ const projectColors = builderProjectColors;
19
+ const fonts = builderFonts;
24
20
 
25
21
  return useMemo(
26
- () => extractTextStyle(node, { appConfig, projectColors }),
27
- // fonts is intentionally included: extractTextStyle resolves weights via store-provided font definitions.
22
+ () => extractTextStyle(node, { appConfig, projectColors, fonts }),
23
+ // fonts is intentionally included: extractTextStyle resolves weights via font definitions.
28
24
  [node, appConfig, projectColors, fonts],
29
25
  );
30
26
  }
@@ -1,28 +1,19 @@
1
1
  import { useMemo } from 'react';
2
2
  import type { NodeData } from '../types/Node';
3
3
  import type { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
4
- import type { AppConfig } from '../types/PreviewConfig';
5
- import type { ProjectColors } from '../types/Project';
6
- import { useRenderStore } from '../store';
4
+ import { useBuilderParams } from '../components/BuilderProvider';
7
5
  import { extractViewStyle } from '../utils/extractViewStyle';
8
-
9
- export type UseExtractViewStyleOptions = {
10
- appConfig?: AppConfig;
11
- projectColors?: ProjectColors;
12
- };
6
+ import { defaultAppConfig } from '../types/PreviewConfig';
13
7
 
14
8
  export function useExtractViewStyle<T extends ViewPropsGenerated['attributes']>(
15
9
  node: NodeData<T>,
16
- options: UseExtractViewStyleOptions = {},
17
10
  ) {
18
- const storeAppConfig = useRenderStore((s) => s.appConfig);
19
- const storeProjectColors = useRenderStore((s) => s.projectColors);
20
-
21
- const appConfig = options.appConfig ?? storeAppConfig;
22
- const projectColors = options.projectColors ?? storeProjectColors;
11
+ const { appConfig, projectColors: builderProjectColors } = useBuilderParams();
12
+ const theme = appConfig?.theme ?? defaultAppConfig.theme;
13
+ const projectColors = builderProjectColors;
23
14
 
24
15
  return useMemo(
25
- () => extractViewStyle(node, { appConfig, projectColors }),
26
- [node, appConfig, projectColors],
16
+ () => extractViewStyle(node, { theme, projectColors }),
17
+ [node, theme, projectColors],
27
18
  );
28
19
  }
@@ -1,15 +1,16 @@
1
1
  import { useCallback } from 'react';
2
- import { useRenderStore } from '../store';
2
+ import type { AppConfig } from '../types/PreviewConfig';
3
+ import { defaultAppConfig } from '../types/PreviewConfig';
4
+ import { useBuilderParams } from '../components/BuilderProvider';
3
5
  import { useLocalizationParams } from './useLocalizationParams';
4
6
  import { replaceLocalizationParams } from '../utils/replaceLocalizationParams';
5
7
 
6
8
  export type LocalizeFn = (keyOrText: string) => string;
7
9
 
8
- export function useLocalize(): LocalizeFn {
9
- const { defaultLanguage, localication } = useRenderStore((s) => ({
10
- defaultLanguage: s.appConfig?.defaultLanguage,
11
- localication: s.appConfig?.localication,
12
- }));
10
+ export function useLocalize(options?: { appConfig?: AppConfig }): LocalizeFn {
11
+ const { appConfig: builderAppConfig } = useBuilderParams();
12
+ const appConfig = options?.appConfig ?? builderAppConfig ?? defaultAppConfig;
13
+ const { defaultLanguage, localication } = appConfig;
13
14
  const params = useLocalizationParams();
14
15
 
15
16
  return useCallback(
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { useRenderStore } from '../store';
2
+ import type { Device } from '../types/Device';
3
3
 
4
4
  function addInset(
5
5
  base: React.CSSProperties['paddingTop'],
@@ -28,19 +28,18 @@ function subtractInset(
28
28
  export function useSafeAreaViewStyle(
29
29
  baseStyle: React.CSSProperties,
30
30
  enabled: boolean,
31
+ device?: Device,
31
32
  ) {
32
- const device = useRenderStore((s) => s.device);
33
-
34
33
  return useMemo(() => {
35
34
  if (!enabled) return baseStyle;
36
35
 
37
- const [insetTop, insetRight, insetBottom, insetLeft] = device.insets ?? [
36
+ const [insetTop, insetRight, insetBottom, insetLeft] = device?.insets ?? [
38
37
  0, 0, 0, 0,
39
38
  ];
40
39
 
41
40
  // Match DeviceMockFrame fallbacks: status bar overlays content, so we treat it as top safe area.
42
41
  const top =
43
- insetTop || (device.platform === 'ios' ? 20 : device.platform ? 24 : 0);
42
+ insetTop || (device?.platform === 'ios' ? 20 : device?.platform ? 24 : 0);
44
43
 
45
44
  // Bottom safe area is handled visually by the mock navigation bar area, which takes layout space.
46
45
  // So we intentionally don't add bottom padding here to avoid double-spacing.
package/src/index.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import './styles/index.scss';
2
2
  import AttributesEditor from './AttributesEditor';
3
+
4
+ // Example app (`example/`) uses these exports today:
5
+ // - `ProjectPage` (from `src/pages/ProjectPage.tsx`)
6
+ // - `getSamples` (from `src/assets/samples/getSamples.ts`)
7
+ // - types from `src/types/*` and `src/types/PreviewConfig.ts` (Project/ProjectColors/AppConfig/defaultAppConfig)
8
+ //
9
+ // React Native: a RN-safe entrypoint exists at `react-builder/native` (`src/index.native.ts`).
10
+ // More RN-focused exports/integration helpers are coming.
3
11
  // NOTE: In React Native, `products` should be sourced from an IAP wrapper (e.g. `react-native-iap`)
4
12
  // and passed into `BuilderProvider` from the host app.
5
13
  export {
@@ -27,11 +35,6 @@ export {
27
35
  analyseNode,
28
36
  } from './utils/analyseNode';
29
37
  export { getSamples } from './assets/samples/getSamples';
30
- export { getBasicSamples } from './assets/samples/getSamples';
31
- export { getOnboardSamples } from './assets/samples/getSamples';
32
- export { RenderPage } from './RenderPage';
33
- export { DeviceMockFrame } from './DeviceMockFrame';
34
- export { novaToJson } from './utils/novaToJson';
35
38
  export type { AppConfig, Localication } from './types/PreviewConfig';
36
39
  export { defaultAppConfig } from './types/PreviewConfig';
37
40
  export { getDevices, getDefaultDevice } from './utils/getDevices';
@@ -52,7 +55,6 @@ export {
52
55
  } from './utils/getImage';
53
56
  export { ProjectPage } from './pages/ProjectPage';
54
57
  export type { ProjectPageProps } from './pages/ProjectPage';
55
- export { copyNode } from './utils/copyNode';
56
58
  export { logger } from './utils/logger';
57
59
  export type { LogLevel } from './types/Project';
58
60
  export type {
@@ -61,7 +63,6 @@ export type {
61
63
  Product as PaywallProduct,
62
64
  PaywallModel,
63
65
  } from './paywall/types/paywall-types';
64
-
65
66
  // Paywall hooks
66
67
  export {
67
68
  usePaywallCounter,
@@ -1,6 +1,5 @@
1
1
  import { type ReactNode, useMemo, useState } from 'react';
2
2
  import { RenderPage } from '../RenderPage';
3
- import { BuilderProvider } from '../components/BuilderProvider';
4
3
  import { RenderErrorBoundary } from '../components/RenderErrorBoundary';
5
4
  import type { Node } from '../types/Node';
6
5
  import type { Product } from '../paywall/types/paywall-types';
@@ -267,20 +266,22 @@ export function ProjectDebug({
267
266
  ['--rb-canvas-bg' as any]: canvasBg ?? 'none',
268
267
  }}
269
268
  >
270
- <BuilderProvider params={{ products, benefits }}>
271
- <RenderErrorBoundary
272
- subtitle="caught by ProjectDebug preview"
273
- onError={(e, componentStack) =>
274
- setPreviewError({
275
- message: e?.message ?? String(e),
276
- stack: e?.stack,
277
- componentStack,
278
- })
279
- }
280
- >
281
- <RenderPage data={previewData} name={name} />
282
- </RenderErrorBoundary>
283
- </BuilderProvider>
269
+ <RenderErrorBoundary
270
+ subtitle="caught by ProjectDebug preview"
271
+ onError={(e, componentStack) =>
272
+ setPreviewError({
273
+ message: e?.message ?? String(e),
274
+ stack: e?.stack,
275
+ componentStack,
276
+ })
277
+ }
278
+ >
279
+ <RenderPage
280
+ data={previewData}
281
+ name={name}
282
+ params={{ products, benefits }}
283
+ />
284
+ </RenderErrorBoundary>
284
285
  </section>
285
286
 
286
287
  <section className="rb-project-debug__error" aria-label="Errors">
@@ -5,7 +5,6 @@ import { ToastContainer, toast } from 'react-toastify';
5
5
  import { RenderPage } from '../RenderPage';
6
6
  import { EditorHeader } from '../components/EditorHeader';
7
7
  import { AttributesEditorPanel } from '../components/AttributesEditorPanel';
8
- import { BuilderProvider } from '../components/BuilderProvider';
9
8
  import { BuilderPanel } from './tabs/BuilderPanel';
10
9
  import { BottomBar } from '../components/BottomBar';
11
10
  import { AppConfig, defaultAppConfig } from '../types/PreviewConfig';
@@ -79,6 +78,7 @@ export function ProjectPage({
79
78
  const resolvedProjectColors = projectColors ?? project.projectColors;
80
79
  const isEmptyProjectData =
81
80
  isNodeNullOrUndefined(project.data) || isEmptyObject(project.data);
81
+ // useRenderStore will be removed
82
82
  const {
83
83
  current,
84
84
  setCurrent,
@@ -86,6 +86,7 @@ export function ProjectPage({
86
86
  setProjectName,
87
87
  products,
88
88
  benefits,
89
+ previewMode,
89
90
  } = useRenderStore((s) => ({
90
91
  current: s.current,
91
92
  setCurrent: s.setCurrent,
@@ -93,6 +94,7 @@ export function ProjectPage({
93
94
  setProjectName: s.setProjectName,
94
95
  products: s.products,
95
96
  benefits: s.benefits,
97
+ previewMode: s.previewMode,
96
98
  }));
97
99
  const [editorData, setEditorData] = useState<Node>(() => {
98
100
  if (!isEmptyProjectData) return null;
@@ -462,17 +464,27 @@ export function ProjectPage({
462
464
  )}
463
465
  {/* NOTE: In React Native, `products` should come from an IAP wrapper (e.g. `react-native-iap`). */}
464
466
  {!showLoading && editorData && (
465
- <BuilderProvider
467
+ <RenderPage
468
+ data={editorData}
469
+ name={resolvedName}
466
470
  params={{
467
471
  products,
468
472
  benefits:
469
473
  benefits && typeof benefits === 'object'
470
474
  ? (benefits as PaywallBenefits)
471
475
  : {},
476
+ // Theme/colors/fonts must be passed via BuilderProvider params so build-components never touch useRenderStore.
477
+ appConfig,
478
+ projectColors: resolvedProjectColors,
479
+ fonts: typography.fonts,
480
+ appFont: resolvedAppFont,
481
+ previewMode,
482
+ selectedKey:
483
+ current && typeof current === 'object' && 'key' in current
484
+ ? ((current as any).key as string | undefined)
485
+ : undefined,
472
486
  }}
473
- >
474
- <RenderPage data={editorData} name={resolvedName} />
475
- </BuilderProvider>
487
+ />
476
488
  )}
477
489
  </div>
478
490
  {/* BOTOM BAR */}
package/src/types/Node.ts CHANGED
@@ -10,6 +10,7 @@ export type Node<T = NodeDefaultAttribute> =
10
10
 
11
11
  export interface NodeData<T = Record<string, unknown>> {
12
12
  type: string;
13
+ sourceType?: string;
13
14
  children: Node<Record<string, unknown>>;
14
15
  attributes?: T;
15
16
  key?: string;
@@ -3,11 +3,11 @@ import type { TextPropsGenerated } from '../../build-components/Text/TextProps.g
3
3
  import type { AppConfig } from '../../types/PreviewConfig';
4
4
  import { defaultAppConfig } from '../../types/PreviewConfig';
5
5
  import type { ProjectColors } from '../../types/Project';
6
+ import type { Fonts } from '../../types/Fonts';
6
7
  import { fs, parseSize } from '../../size-matters';
7
8
  import { parseColor } from '../parseColor';
8
9
  import { extractViewStyle } from '../extractViewStyle';
9
10
  import { normalizeFontWeight } from '../fontWeight';
10
- import { useRenderStore } from '../../store';
11
11
  import {
12
12
  findFontDefinition,
13
13
  loadFontFamily,
@@ -26,21 +26,29 @@ function weightToNumericKey(weight: unknown): string | undefined {
26
26
  return normalized;
27
27
  }
28
28
 
29
- function ensureFontWeightLoaded(familyName: string, weightKey?: string) {
29
+ function ensureFontWeightLoaded(
30
+ familyName: string,
31
+ weightKey: string | undefined,
32
+ options: {
33
+ fonts?: Fonts;
34
+ onFontLoaded?: (fontFamily: string) => void;
35
+ onError?: (error: string) => void;
36
+ },
37
+ ) {
30
38
  if (typeof document === 'undefined') return;
31
39
  const name = familyName.trim();
32
40
  if (!name) return;
33
41
  const weight = weightKey?.trim() || '400';
34
42
  const cacheKey = `${name}@${weight}`;
35
43
  if (inFlightFontLoads.has(cacheKey)) return;
44
+ if (!options.fonts) return;
36
45
 
37
46
  fontsDebug.info('extractTextStyle: ensureFontWeightLoaded', {
38
47
  familyName: name,
39
48
  weight,
40
49
  });
41
50
 
42
- const { fonts, markFontLoaded, addError } = useRenderStore.getState();
43
- const promise = loadFontFamily(fonts, name, {
51
+ const promise = loadFontFamily(options.fonts, name, {
44
52
  preferWeight: weight,
45
53
  forceFetch: true,
46
54
  })
@@ -49,14 +57,14 @@ function ensureFontWeightLoaded(familyName: string, weightKey?: string) {
49
57
  familyName: name,
50
58
  weight,
51
59
  });
52
- markFontLoaded(name);
60
+ options.onFontLoaded?.(name);
53
61
  })
54
62
  .catch((e) => {
55
63
  fontsDebug.compactError('extractTextStyle: font weight load failed', e, {
56
64
  familyName: name,
57
65
  weight,
58
66
  });
59
- addError(
67
+ options.onError?.(
60
68
  `Failed to load font "${name}" (weight ${weight}): ${
61
69
  e instanceof Error ? e.message : String(e)
62
70
  }`,
@@ -72,6 +80,9 @@ function ensureFontWeightLoaded(familyName: string, weightKey?: string) {
72
80
  export type ExtractTextStyleOptions = {
73
81
  appConfig?: AppConfig;
74
82
  projectColors?: ProjectColors;
83
+ fonts?: Fonts;
84
+ onFontLoaded?: (fontFamily: string) => void;
85
+ onError?: (error: string) => void;
75
86
  };
76
87
 
77
88
  export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
@@ -119,8 +130,7 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
119
130
  : undefined;
120
131
  if (normalizedFontFamily) {
121
132
  // Resolve "closest available" weight for this family (e.g. if requested 100 but family starts at 300).
122
- const { fonts } = useRenderStore.getState();
123
- const def = findFontDefinition(fonts, normalizedFontFamily);
133
+ const def = findFontDefinition(options.fonts ?? [], normalizedFontFamily);
124
134
  const resolvedWeightKey =
125
135
  def?.family && typeof def.family === 'object'
126
136
  ? resolveClosestFontWeightKey(
@@ -137,7 +147,12 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
137
147
 
138
148
  style.fontFamily = `"${normalizedFontFamily}"`;
139
149
  // Ensure the correct weight file is available (lazy-load per weight).
140
- ensureFontWeightLoaded(normalizedFontFamily, resolvedWeightKey);
150
+ //Optimzation trade off by readability: we only attempt font loading when a fonts registry is provided.
151
+ ensureFontWeightLoaded(normalizedFontFamily, resolvedWeightKey, {
152
+ fonts: options.fonts,
153
+ onFontLoaded: options.onFontLoaded,
154
+ onError: options.onError,
155
+ });
141
156
  // Important: set fontWeight to the actual weight we loaded so CSS requests match loaded face.
142
157
  if (resolvedWeightKey) {
143
158
  style.fontWeight = resolvedWeightKey;
@@ -147,14 +162,18 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
147
162
  // If no fontFamily is set, keep previous behavior.
148
163
  if (!normalizedFontFamily && normalizedFontWeight)
149
164
  style.fontWeight = normalizedFontWeight;
150
- const resolvedTextColor = parseColor(get('color') as any, {
165
+ const resolvedTextColor = parseColor(get('color'), {
151
166
  projectColors: options.projectColors,
152
- appConfig: resolvedAppConfig,
167
+ theme,
153
168
  });
154
169
  style.color = resolvedTextColor ?? fallbackColor;
155
- const textAlign = get('textAlign') as any;
170
+ const textAlign = get('textAlign');
156
171
  if (textAlign)
157
172
  style.textAlign = textAlign as React.CSSProperties['textAlign'];
158
173
 
159
- return { ...extractViewStyle(node, options), ...style };
174
+ const viewStyle = extractViewStyle(node, {
175
+ projectColors: options.projectColors,
176
+ theme,
177
+ });
178
+ return { ...viewStyle, ...style };
160
179
  }
@@ -1,13 +1,12 @@
1
1
  import { ViewPropsGenerated } from '../../build-components/View/ViewProps.generated';
2
2
  import type { NodeData } from '../../types/Node';
3
- import type { AppConfig } from '../../types/PreviewConfig';
4
3
  import type { ProjectColors } from '../../types/Project';
5
4
  import { parseSize } from '../../size-matters';
6
5
  import { parseColor } from '../parseColor';
7
6
 
8
7
  export type ExtractViewStyleOptions = {
9
- appConfig?: AppConfig;
10
8
  projectColors?: ProjectColors;
9
+ theme?: string;
11
10
  };
12
11
 
13
12
  export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
@@ -120,7 +119,7 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
120
119
  style.backgroundColor =
121
120
  parseColor(backgroundColor, {
122
121
  projectColors: options.projectColors,
123
- appConfig: options.appConfig,
122
+ theme: options.theme,
124
123
  }) ?? backgroundColor;
125
124
  }
126
125
  setParsedSize('borderRadius', get('borderRadius') as any);
@@ -1,4 +1,3 @@
1
- import type { AppConfig } from '../types/PreviewConfig';
2
1
  import type { ProjectColors } from '../types/Project';
3
2
 
4
3
  const STATIC_PREFIX = 'STATIC_COLORS.';
@@ -6,7 +5,7 @@ const THEME_PREFIX = 'THEME_COLORS.';
6
5
 
7
6
  export type ParseColorOptions = {
8
7
  projectColors?: ProjectColors;
9
- appConfig?: AppConfig;
8
+ theme?: string;
10
9
  };
11
10
 
12
11
  export function parseColor(value?: string, options: ParseColorOptions = {}) {
@@ -14,7 +13,7 @@ export function parseColor(value?: string, options: ParseColorOptions = {}) {
14
13
  const trimmed = value.trim();
15
14
  if (!trimmed) return undefined;
16
15
 
17
- const { projectColors, appConfig } = options;
16
+ const { projectColors, theme } = options;
18
17
  if (!projectColors) return trimmed;
19
18
 
20
19
  if (trimmed.startsWith(STATIC_PREFIX)) {
@@ -29,8 +28,8 @@ export function parseColor(value?: string, options: ParseColorOptions = {}) {
29
28
  const token = trimmed.slice(THEME_PREFIX.length);
30
29
  if (!token) return trimmed;
31
30
 
32
- const theme = appConfig?.theme ?? 'light';
33
- const themeTokens = projectColors.THEME_COLORS?.[theme];
31
+ const resolvedTheme = theme ?? 'light';
32
+ const themeTokens = projectColors.THEME_COLORS?.[resolvedTheme];
34
33
  const resolved = themeTokens?.[token];
35
34
  if (typeof resolved === 'string' && resolved.trim()) {
36
35
  return resolved.trim();