@developer_tribe/react-builder 1.2.1 → 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 (67) 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 +3 -0
  6. package/dist/hooks/useExtractViewStyle.d.ts +3 -0
  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 +2 -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.d.ts +1 -0
  17. package/dist/index.native.esm.js +1 -28
  18. package/dist/index.native.esm.js.map +1 -1
  19. package/dist/types/Node.d.ts +1 -0
  20. package/dist/utils/extractTextStyle/extractTextStyle.d.ts +13 -0
  21. package/dist/utils/extractTextStyle.d.ts +2 -10
  22. package/dist/utils/extractViewStyle/extractViewStyle.d.ts +8 -0
  23. package/dist/utils/extractViewStyle.d.ts +2 -9
  24. package/dist/utils/parseColor.d.ts +1 -2
  25. package/package.json +1 -1
  26. package/src/RenderPage.tsx +32 -20
  27. package/src/build-components/BIcon/BIcon.tsx +10 -17
  28. package/src/build-components/BackgroundImage/BackgroundImage.tsx +10 -17
  29. package/src/build-components/Button/Button.tsx +23 -27
  30. package/src/build-components/Carousel/Carousel.tsx +10 -16
  31. package/src/build-components/CarouselButtons/CarouselButtons.tsx +4 -13
  32. package/src/build-components/CarouselDots/CarouselDots.tsx +5 -14
  33. package/src/build-components/CarouselItem/CarouselItem.tsx +4 -13
  34. package/src/build-components/CarouselProvider/CarouselProvider.tsx +3 -12
  35. package/src/build-components/Image/Image.tsx +11 -13
  36. package/src/build-components/Main/Main.tsx +12 -24
  37. package/src/build-components/OnboardButton/OnboardButton.tsx +8 -16
  38. package/src/build-components/OnboardButtons/OnboardButtons.tsx +9 -16
  39. package/src/build-components/OnboardDot/OnboardDot.tsx +25 -21
  40. package/src/build-components/OnboardFooter/OnboardFooter.tsx +18 -22
  41. package/src/build-components/OnboardImage/OnboardImage.tsx +12 -14
  42. package/src/build-components/OnboardItem/OnboardItem.tsx +3 -12
  43. package/src/build-components/OnboardProvider/OnboardProvider.tsx +11 -18
  44. package/src/build-components/PaywallBackground/PaywallBackground.tsx +9 -15
  45. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +10 -17
  46. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +12 -18
  47. package/src/build-components/PaywallProvider/PaywallProvider.tsx +16 -20
  48. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +11 -17
  49. package/src/build-components/RadioButton/RadioButton.tsx +12 -21
  50. package/src/build-components/Text/Text.tsx +14 -25
  51. package/src/build-components/View/View.tsx +11 -17
  52. package/src/build-components/other.tsx +1 -1
  53. package/src/components/BuilderProvider.tsx +38 -0
  54. package/src/hooks/useExtractTextStyle.ts +26 -0
  55. package/src/hooks/useExtractViewStyle.ts +19 -0
  56. package/src/hooks/useLocalize.ts +7 -6
  57. package/src/hooks/useSafeAreaViewStyle.ts +4 -5
  58. package/src/index.native.ts +3 -0
  59. package/src/index.ts +10 -7
  60. package/src/pages/ProjectDebug.tsx +16 -15
  61. package/src/pages/ProjectPage.tsx +17 -5
  62. package/src/types/Node.ts +1 -0
  63. package/src/utils/extractTextStyle/extractTextStyle.ts +179 -0
  64. package/src/utils/extractTextStyle.ts +2 -160
  65. package/src/utils/extractViewStyle/extractViewStyle.ts +144 -0
  66. package/src/utils/extractViewStyle.ts +2 -145
  67. package/src/utils/parseColor.ts +4 -5
@@ -0,0 +1,179 @@
1
+ import type { NodeData } from '../../types/Node';
2
+ import type { TextPropsGenerated } from '../../build-components/Text/TextProps.generated';
3
+ import type { AppConfig } from '../../types/PreviewConfig';
4
+ import { defaultAppConfig } from '../../types/PreviewConfig';
5
+ import type { ProjectColors } from '../../types/Project';
6
+ import type { Fonts } from '../../types/Fonts';
7
+ import { fs, parseSize } from '../../size-matters';
8
+ import { parseColor } from '../parseColor';
9
+ import { extractViewStyle } from '../extractViewStyle';
10
+ import { normalizeFontWeight } from '../fontWeight';
11
+ import {
12
+ findFontDefinition,
13
+ loadFontFamily,
14
+ resolveClosestFontWeightKey,
15
+ } from '../loadFontFamily';
16
+ import { fontsDebug } from '../fontsDebug';
17
+
18
+ const inFlightFontLoads: Map<string, Promise<void>> = new Map();
19
+
20
+ function weightToNumericKey(weight: unknown): string | undefined {
21
+ const normalized = normalizeFontWeight(weight);
22
+ if (!normalized) return undefined;
23
+ if (normalized === 'normal') return '400';
24
+ if (normalized === 'bold') return '700';
25
+ // already '100'..'900'
26
+ return normalized;
27
+ }
28
+
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
+ ) {
38
+ if (typeof document === 'undefined') return;
39
+ const name = familyName.trim();
40
+ if (!name) return;
41
+ const weight = weightKey?.trim() || '400';
42
+ const cacheKey = `${name}@${weight}`;
43
+ if (inFlightFontLoads.has(cacheKey)) return;
44
+ if (!options.fonts) return;
45
+
46
+ fontsDebug.info('extractTextStyle: ensureFontWeightLoaded', {
47
+ familyName: name,
48
+ weight,
49
+ });
50
+
51
+ const promise = loadFontFamily(options.fonts, name, {
52
+ preferWeight: weight,
53
+ forceFetch: true,
54
+ })
55
+ .then(() => {
56
+ fontsDebug.info('extractTextStyle: font weight loaded', {
57
+ familyName: name,
58
+ weight,
59
+ });
60
+ options.onFontLoaded?.(name);
61
+ })
62
+ .catch((e) => {
63
+ fontsDebug.compactError('extractTextStyle: font weight load failed', e, {
64
+ familyName: name,
65
+ weight,
66
+ });
67
+ options.onError?.(
68
+ `Failed to load font "${name}" (weight ${weight}): ${
69
+ e instanceof Error ? e.message : String(e)
70
+ }`,
71
+ );
72
+ })
73
+ .finally(() => {
74
+ inFlightFontLoads.delete(cacheKey);
75
+ });
76
+
77
+ inFlightFontLoads.set(cacheKey, promise);
78
+ }
79
+
80
+ export type ExtractTextStyleOptions = {
81
+ appConfig?: AppConfig;
82
+ projectColors?: ProjectColors;
83
+ fonts?: Fonts;
84
+ onFontLoaded?: (fontFamily: string) => void;
85
+ onError?: (error: string) => void;
86
+ };
87
+
88
+ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
89
+ node: NodeData<T>,
90
+ options: ExtractTextStyleOptions = {},
91
+ ) {
92
+ const attributes = node.attributes;
93
+ const styleBag = (attributes as any)?.style as
94
+ | Record<string, unknown>
95
+ | undefined;
96
+ const get = <K extends keyof TextPropsGenerated['attributes']>(
97
+ key: K,
98
+ ): TextPropsGenerated['attributes'][K] | undefined => {
99
+ const direct = (attributes as any)?.[key];
100
+ if (direct !== undefined && direct !== null) return direct;
101
+ return styleBag?.[key as unknown as string] as any;
102
+ };
103
+ const resolvedAppConfig = options.appConfig ?? defaultAppConfig;
104
+ const { screenStyle, theme } = resolvedAppConfig;
105
+ const fallbackColor =
106
+ theme === 'light' ? screenStyle.light.color : screenStyle.dark.color;
107
+
108
+ const style: React.CSSProperties = {};
109
+
110
+ if (!attributes) {
111
+ style.fontSize = fs(14);
112
+ style.color = fallbackColor;
113
+ return style;
114
+ }
115
+
116
+ // Typography
117
+ const fontSize = get('fontSize') as any;
118
+ if (fontSize !== undefined) {
119
+ const parsed = parseSize(fontSize);
120
+ style.fontSize = parsed as React.CSSProperties['fontSize'];
121
+ } else {
122
+ style.fontSize = fs(14);
123
+ }
124
+ const fontFamily = get('fontFamily') as any;
125
+ const fontWeight = get('fontWeight') as any;
126
+ const requestedWeight = weightToNumericKey(fontWeight);
127
+ const normalizedFontFamily =
128
+ typeof fontFamily === 'string' && fontFamily.trim().length > 0
129
+ ? fontFamily.trim()
130
+ : undefined;
131
+ if (normalizedFontFamily) {
132
+ // Resolve "closest available" weight for this family (e.g. if requested 100 but family starts at 300).
133
+ const def = findFontDefinition(options.fonts ?? [], normalizedFontFamily);
134
+ const resolvedWeightKey =
135
+ def?.family && typeof def.family === 'object'
136
+ ? resolveClosestFontWeightKey(
137
+ def.family as Record<string, string>,
138
+ requestedWeight,
139
+ )
140
+ : requestedWeight;
141
+
142
+ fontsDebug.info('extractTextStyle: resolved font family/weight', {
143
+ fontFamily: normalizedFontFamily,
144
+ requestedWeight,
145
+ resolvedWeightKey,
146
+ });
147
+
148
+ style.fontFamily = `"${normalizedFontFamily}"`;
149
+ // Ensure the correct weight file is available (lazy-load per weight).
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
+ });
156
+ // Important: set fontWeight to the actual weight we loaded so CSS requests match loaded face.
157
+ if (resolvedWeightKey) {
158
+ style.fontWeight = resolvedWeightKey;
159
+ }
160
+ }
161
+ const normalizedFontWeight = normalizeFontWeight(fontWeight);
162
+ // If no fontFamily is set, keep previous behavior.
163
+ if (!normalizedFontFamily && normalizedFontWeight)
164
+ style.fontWeight = normalizedFontWeight;
165
+ const resolvedTextColor = parseColor(get('color'), {
166
+ projectColors: options.projectColors,
167
+ theme,
168
+ });
169
+ style.color = resolvedTextColor ?? fallbackColor;
170
+ const textAlign = get('textAlign');
171
+ if (textAlign)
172
+ style.textAlign = textAlign as React.CSSProperties['textAlign'];
173
+
174
+ const viewStyle = extractViewStyle(node, {
175
+ projectColors: options.projectColors,
176
+ theme,
177
+ });
178
+ return { ...viewStyle, ...style };
179
+ }
@@ -1,160 +1,2 @@
1
- import type { NodeData } from '../types/Node';
2
- import type { TextPropsGenerated } from '../build-components/Text/TextProps.generated';
3
- import type { AppConfig } from '../types/PreviewConfig';
4
- import { defaultAppConfig } from '../types/PreviewConfig';
5
- import type { ProjectColors } from '../types/Project';
6
- import { fs, parseSize } from '../size-matters';
7
- import { parseColor } from './parseColor';
8
- import { extractViewStyle } from './extractViewStyle';
9
- import { normalizeFontWeight } from './fontWeight';
10
- import { useRenderStore } from '../store';
11
- import {
12
- findFontDefinition,
13
- loadFontFamily,
14
- resolveClosestFontWeightKey,
15
- } from './loadFontFamily';
16
- import { fontsDebug } from './fontsDebug';
17
-
18
- const inFlightFontLoads: Map<string, Promise<void>> = new Map();
19
-
20
- function weightToNumericKey(weight: unknown): string | undefined {
21
- const normalized = normalizeFontWeight(weight);
22
- if (!normalized) return undefined;
23
- if (normalized === 'normal') return '400';
24
- if (normalized === 'bold') return '700';
25
- // already '100'..'900'
26
- return normalized;
27
- }
28
-
29
- function ensureFontWeightLoaded(familyName: string, weightKey?: string) {
30
- if (typeof document === 'undefined') return;
31
- const name = familyName.trim();
32
- if (!name) return;
33
- const weight = weightKey?.trim() || '400';
34
- const cacheKey = `${name}@${weight}`;
35
- if (inFlightFontLoads.has(cacheKey)) return;
36
-
37
- fontsDebug.info('extractTextStyle: ensureFontWeightLoaded', {
38
- familyName: name,
39
- weight,
40
- });
41
-
42
- const { fonts, markFontLoaded, addError } = useRenderStore.getState();
43
- const promise = loadFontFamily(fonts, name, {
44
- preferWeight: weight,
45
- forceFetch: true,
46
- })
47
- .then(() => {
48
- fontsDebug.info('extractTextStyle: font weight loaded', {
49
- familyName: name,
50
- weight,
51
- });
52
- markFontLoaded(name);
53
- })
54
- .catch((e) => {
55
- fontsDebug.compactError('extractTextStyle: font weight load failed', e, {
56
- familyName: name,
57
- weight,
58
- });
59
- addError(
60
- `Failed to load font "${name}" (weight ${weight}): ${
61
- e instanceof Error ? e.message : String(e)
62
- }`,
63
- );
64
- })
65
- .finally(() => {
66
- inFlightFontLoads.delete(cacheKey);
67
- });
68
-
69
- inFlightFontLoads.set(cacheKey, promise);
70
- }
71
-
72
- type ExtractTextStyleOptions = {
73
- appConfig?: AppConfig;
74
- projectColors?: ProjectColors;
75
- };
76
-
77
- export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
78
- node: NodeData<T>,
79
- options: ExtractTextStyleOptions = {},
80
- ) {
81
- const attributes = node.attributes;
82
- const styleBag = (attributes as any)?.style as
83
- | Record<string, unknown>
84
- | undefined;
85
- const get = <K extends keyof TextPropsGenerated['attributes']>(
86
- key: K,
87
- ): TextPropsGenerated['attributes'][K] | undefined => {
88
- const direct = (attributes as any)?.[key];
89
- if (direct !== undefined && direct !== null) return direct;
90
- return styleBag?.[key as unknown as string] as any;
91
- };
92
- const resolvedAppConfig = options.appConfig ?? defaultAppConfig;
93
- const { screenStyle, theme } = resolvedAppConfig;
94
- const fallbackColor =
95
- theme === 'light' ? screenStyle.light.color : screenStyle.dark.color;
96
-
97
- const style: React.CSSProperties = {};
98
-
99
- if (!attributes) {
100
- style.fontSize = fs(14);
101
- style.color = fallbackColor;
102
- return style;
103
- }
104
-
105
- // Typography
106
- const fontSize = get('fontSize') as any;
107
- if (fontSize !== undefined) {
108
- const parsed = parseSize(fontSize);
109
- style.fontSize = parsed as React.CSSProperties['fontSize'];
110
- } else {
111
- style.fontSize = fs(14);
112
- }
113
- const fontFamily = get('fontFamily') as any;
114
- const fontWeight = get('fontWeight') as any;
115
- const requestedWeight = weightToNumericKey(fontWeight);
116
- const normalizedFontFamily =
117
- typeof fontFamily === 'string' && fontFamily.trim().length > 0
118
- ? fontFamily.trim()
119
- : undefined;
120
- if (normalizedFontFamily) {
121
- // 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);
124
- const resolvedWeightKey =
125
- def?.family && typeof def.family === 'object'
126
- ? resolveClosestFontWeightKey(
127
- def.family as Record<string, string>,
128
- requestedWeight,
129
- )
130
- : requestedWeight;
131
-
132
- fontsDebug.info('extractTextStyle: resolved font family/weight', {
133
- fontFamily: normalizedFontFamily,
134
- requestedWeight,
135
- resolvedWeightKey,
136
- });
137
-
138
- style.fontFamily = `"${normalizedFontFamily}"`;
139
- // Ensure the correct weight file is available (lazy-load per weight).
140
- ensureFontWeightLoaded(normalizedFontFamily, resolvedWeightKey);
141
- // Important: set fontWeight to the actual weight we loaded so CSS requests match loaded face.
142
- if (resolvedWeightKey) {
143
- style.fontWeight = resolvedWeightKey;
144
- }
145
- }
146
- const normalizedFontWeight = normalizeFontWeight(fontWeight);
147
- // If no fontFamily is set, keep previous behavior.
148
- if (!normalizedFontFamily && normalizedFontWeight)
149
- style.fontWeight = normalizedFontWeight;
150
- const resolvedTextColor = parseColor(get('color') as any, {
151
- projectColors: options.projectColors,
152
- appConfig: resolvedAppConfig,
153
- });
154
- style.color = resolvedTextColor ?? fallbackColor;
155
- const textAlign = get('textAlign') as any;
156
- if (textAlign)
157
- style.textAlign = textAlign as React.CSSProperties['textAlign'];
158
-
159
- return { ...extractViewStyle(node, options), ...style };
160
- }
1
+ export type { ExtractTextStyleOptions } from './extractTextStyle/extractTextStyle';
2
+ export { extractTextStyle } from './extractTextStyle/extractTextStyle';
@@ -0,0 +1,144 @@
1
+ import { ViewPropsGenerated } from '../../build-components/View/ViewProps.generated';
2
+ import type { NodeData } from '../../types/Node';
3
+ import type { ProjectColors } from '../../types/Project';
4
+ import { parseSize } from '../../size-matters';
5
+ import { parseColor } from '../parseColor';
6
+
7
+ export type ExtractViewStyleOptions = {
8
+ projectColors?: ProjectColors;
9
+ theme?: string;
10
+ };
11
+
12
+ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
13
+ node: NodeData<T>,
14
+ options: ExtractViewStyleOptions = {},
15
+ ) {
16
+ const attributes = node.attributes;
17
+ const styleBag = (attributes as any)?.style as
18
+ | Record<string, unknown>
19
+ | undefined;
20
+ const get = <K extends keyof ViewPropsGenerated['attributes']>(
21
+ key: K,
22
+ ): ViewPropsGenerated['attributes'][K] | undefined => {
23
+ const direct = (attributes as any)?.[key];
24
+ if (direct !== undefined && direct !== null) return direct;
25
+ return styleBag?.[key as unknown as string] as any;
26
+ };
27
+
28
+ const scrollable = (get('scrollable') as any) ?? false;
29
+ const style: React.CSSProperties = {
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ };
33
+ if (!attributes) return style;
34
+ const isEmptySizeValue = (value: unknown) =>
35
+ value === undefined ||
36
+ value === null ||
37
+ (typeof value === 'string' && value.trim() === '');
38
+ if (scrollable) {
39
+ // Important for flex children: allow the element to shrink so overflow scroll can work.
40
+ style.minWidth = 0;
41
+ style.minHeight = 0;
42
+ if (get('flexDirection') === 'row') {
43
+ style.overflowX = 'auto';
44
+ style.overflowY = 'hidden';
45
+ style.maxWidth = '100%';
46
+ style.maxHeight = '100%';
47
+ } else {
48
+ style.overflowY = 'auto';
49
+ style.overflowX = 'hidden';
50
+ style.maxHeight = '100%';
51
+ style.maxWidth = '100%';
52
+ }
53
+ }
54
+ const flexDirection = get('flexDirection');
55
+ if (flexDirection) style.flexDirection = flexDirection as any;
56
+ const alignItems = get('alignItems');
57
+ if (alignItems)
58
+ style.alignItems = alignItems as React.CSSProperties['alignItems'];
59
+ const justifyContent = get('justifyContent');
60
+ if (justifyContent)
61
+ style.justifyContent =
62
+ justifyContent as React.CSSProperties['justifyContent'];
63
+ const setParsedSize = <K extends keyof React.CSSProperties>(
64
+ property: K,
65
+ rawValue: string | number | undefined,
66
+ ) => {
67
+ if (isEmptySizeValue(rawValue)) return;
68
+ const parsed = parseSize(rawValue);
69
+ style[property] = parsed as React.CSSProperties[K];
70
+ };
71
+
72
+ setParsedSize('gap', get('gap') as any);
73
+ setParsedSize('padding', get('padding') as any);
74
+ setParsedSize('margin', get('margin') as any);
75
+
76
+ const paddingHorizontal = get('paddingHorizontal') as any;
77
+ if (!isEmptySizeValue(paddingHorizontal)) {
78
+ const parsed = parseSize(paddingHorizontal);
79
+ style.paddingLeft = parsed as React.CSSProperties['paddingLeft'];
80
+ style.paddingRight = parsed as React.CSSProperties['paddingRight'];
81
+ }
82
+ const paddingVertical = get('paddingVertical') as any;
83
+ if (!isEmptySizeValue(paddingVertical)) {
84
+ const parsed = parseSize(paddingVertical);
85
+ style.paddingTop = parsed as React.CSSProperties['paddingTop'];
86
+ style.paddingBottom = parsed as React.CSSProperties['paddingBottom'];
87
+ }
88
+
89
+ // Explicit per-side paddings should override paddingHorizontal/paddingVertical.
90
+ setParsedSize('paddingTop', get('paddingTop') as any);
91
+ setParsedSize('paddingBottom', get('paddingBottom') as any);
92
+ setParsedSize('paddingLeft', get('paddingLeft') as any);
93
+ setParsedSize('paddingRight', get('paddingRight') as any);
94
+
95
+ const marginHorizontalRaw =
96
+ ((attributes as Record<string, unknown>).marginHorizontal as
97
+ | string
98
+ | number
99
+ | undefined) ?? (styleBag?.marginHorizontal as any);
100
+ if (!isEmptySizeValue(marginHorizontalRaw)) {
101
+ const parsed = parseSize(marginHorizontalRaw);
102
+ style.marginLeft = parsed as React.CSSProperties['marginLeft'];
103
+ style.marginRight = parsed as React.CSSProperties['marginRight'];
104
+ }
105
+
106
+ const marginVertical = get('marginVertical') as any;
107
+ if (!isEmptySizeValue(marginVertical)) {
108
+ const parsed = parseSize(marginVertical);
109
+ style.marginTop = parsed as React.CSSProperties['marginTop'];
110
+ style.marginBottom = parsed as React.CSSProperties['marginBottom'];
111
+ }
112
+
113
+ setParsedSize('marginTop', get('marginTop') as any);
114
+ setParsedSize('marginBottom', get('marginBottom') as any);
115
+ setParsedSize('marginLeft', get('marginLeft') as any);
116
+ setParsedSize('marginRight', get('marginRight') as any);
117
+ const backgroundColor = get('backgroundColor') as any;
118
+ if (backgroundColor) {
119
+ style.backgroundColor =
120
+ parseColor(backgroundColor, {
121
+ projectColors: options.projectColors,
122
+ theme: options.theme,
123
+ }) ?? backgroundColor;
124
+ }
125
+ setParsedSize('borderRadius', get('borderRadius') as any);
126
+ setParsedSize('width', get('width') as any);
127
+ setParsedSize('minWidth', get('minWidth') as any);
128
+ setParsedSize('maxWidth', get('maxWidth') as any);
129
+ setParsedSize('height', get('height') as any);
130
+ setParsedSize('minHeight', get('minHeight') as any);
131
+ setParsedSize('maxHeight', get('maxHeight') as any);
132
+ const flex = get('flex') as any;
133
+ if (flex !== undefined) style.flex = flex as React.CSSProperties['flex'];
134
+ const position = get('position') as any;
135
+ if (position) style.position = position as React.CSSProperties['position'];
136
+ setParsedSize('top', get('top') as any);
137
+ setParsedSize('bottom', get('bottom') as any);
138
+ setParsedSize('left', get('left') as any);
139
+ setParsedSize('right', get('right') as any);
140
+ const zIndex = get('zIndex') as any;
141
+ if (zIndex !== undefined)
142
+ style.zIndex = zIndex as React.CSSProperties['zIndex'];
143
+ return style;
144
+ }
@@ -1,145 +1,2 @@
1
- import { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
2
- import type { NodeData } from '../types/Node';
3
- import type { AppConfig } from '../types/PreviewConfig';
4
- import type { ProjectColors } from '../types/Project';
5
- import { parseSize } from '../size-matters';
6
- import { parseColor } from './parseColor';
7
-
8
- export type ExtractViewStyleOptions = {
9
- appConfig?: AppConfig;
10
- projectColors?: ProjectColors;
11
- };
12
-
13
- export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
14
- node: NodeData<T>,
15
- options: ExtractViewStyleOptions = {},
16
- ) {
17
- const attributes = node.attributes;
18
- const styleBag = (attributes as any)?.style as
19
- | Record<string, unknown>
20
- | undefined;
21
- const get = <K extends keyof ViewPropsGenerated['attributes']>(
22
- key: K,
23
- ): ViewPropsGenerated['attributes'][K] | undefined => {
24
- const direct = (attributes as any)?.[key];
25
- if (direct !== undefined && direct !== null) return direct;
26
- return styleBag?.[key as unknown as string] as any;
27
- };
28
-
29
- const scrollable = (get('scrollable') as any) ?? false;
30
- const style: React.CSSProperties = {
31
- display: 'flex',
32
- flexDirection: 'column',
33
- };
34
- if (!attributes) return style;
35
- const isEmptySizeValue = (value: unknown) =>
36
- value === undefined ||
37
- value === null ||
38
- (typeof value === 'string' && value.trim() === '');
39
- if (scrollable) {
40
- // Important for flex children: allow the element to shrink so overflow scroll can work.
41
- style.minWidth = 0;
42
- style.minHeight = 0;
43
- if (get('flexDirection') === 'row') {
44
- style.overflowX = 'auto';
45
- style.overflowY = 'hidden';
46
- style.maxWidth = '100%';
47
- style.maxHeight = '100%';
48
- } else {
49
- style.overflowY = 'auto';
50
- style.overflowX = 'hidden';
51
- style.maxHeight = '100%';
52
- style.maxWidth = '100%';
53
- }
54
- }
55
- const flexDirection = get('flexDirection');
56
- if (flexDirection) style.flexDirection = flexDirection as any;
57
- const alignItems = get('alignItems');
58
- if (alignItems)
59
- style.alignItems = alignItems as React.CSSProperties['alignItems'];
60
- const justifyContent = get('justifyContent');
61
- if (justifyContent)
62
- style.justifyContent =
63
- justifyContent as React.CSSProperties['justifyContent'];
64
- const setParsedSize = <K extends keyof React.CSSProperties>(
65
- property: K,
66
- rawValue: string | number | undefined,
67
- ) => {
68
- if (isEmptySizeValue(rawValue)) return;
69
- const parsed = parseSize(rawValue);
70
- style[property] = parsed as React.CSSProperties[K];
71
- };
72
-
73
- setParsedSize('gap', get('gap') as any);
74
- setParsedSize('padding', get('padding') as any);
75
- setParsedSize('margin', get('margin') as any);
76
-
77
- const paddingHorizontal = get('paddingHorizontal') as any;
78
- if (!isEmptySizeValue(paddingHorizontal)) {
79
- const parsed = parseSize(paddingHorizontal);
80
- style.paddingLeft = parsed as React.CSSProperties['paddingLeft'];
81
- style.paddingRight = parsed as React.CSSProperties['paddingRight'];
82
- }
83
- const paddingVertical = get('paddingVertical') as any;
84
- if (!isEmptySizeValue(paddingVertical)) {
85
- const parsed = parseSize(paddingVertical);
86
- style.paddingTop = parsed as React.CSSProperties['paddingTop'];
87
- style.paddingBottom = parsed as React.CSSProperties['paddingBottom'];
88
- }
89
-
90
- // Explicit per-side paddings should override paddingHorizontal/paddingVertical.
91
- setParsedSize('paddingTop', get('paddingTop') as any);
92
- setParsedSize('paddingBottom', get('paddingBottom') as any);
93
- setParsedSize('paddingLeft', get('paddingLeft') as any);
94
- setParsedSize('paddingRight', get('paddingRight') as any);
95
-
96
- const marginHorizontalRaw =
97
- ((attributes as Record<string, unknown>).marginHorizontal as
98
- | string
99
- | number
100
- | undefined) ?? (styleBag?.marginHorizontal as any);
101
- if (!isEmptySizeValue(marginHorizontalRaw)) {
102
- const parsed = parseSize(marginHorizontalRaw);
103
- style.marginLeft = parsed as React.CSSProperties['marginLeft'];
104
- style.marginRight = parsed as React.CSSProperties['marginRight'];
105
- }
106
-
107
- const marginVertical = get('marginVertical') as any;
108
- if (!isEmptySizeValue(marginVertical)) {
109
- const parsed = parseSize(marginVertical);
110
- style.marginTop = parsed as React.CSSProperties['marginTop'];
111
- style.marginBottom = parsed as React.CSSProperties['marginBottom'];
112
- }
113
-
114
- setParsedSize('marginTop', get('marginTop') as any);
115
- setParsedSize('marginBottom', get('marginBottom') as any);
116
- setParsedSize('marginLeft', get('marginLeft') as any);
117
- setParsedSize('marginRight', get('marginRight') as any);
118
- const backgroundColor = get('backgroundColor') as any;
119
- if (backgroundColor) {
120
- style.backgroundColor =
121
- parseColor(backgroundColor, {
122
- projectColors: options.projectColors,
123
- appConfig: options.appConfig,
124
- }) ?? backgroundColor;
125
- }
126
- setParsedSize('borderRadius', get('borderRadius') as any);
127
- setParsedSize('width', get('width') as any);
128
- setParsedSize('minWidth', get('minWidth') as any);
129
- setParsedSize('maxWidth', get('maxWidth') as any);
130
- setParsedSize('height', get('height') as any);
131
- setParsedSize('minHeight', get('minHeight') as any);
132
- setParsedSize('maxHeight', get('maxHeight') as any);
133
- const flex = get('flex') as any;
134
- if (flex !== undefined) style.flex = flex as React.CSSProperties['flex'];
135
- const position = get('position') as any;
136
- if (position) style.position = position as React.CSSProperties['position'];
137
- setParsedSize('top', get('top') as any);
138
- setParsedSize('bottom', get('bottom') as any);
139
- setParsedSize('left', get('left') as any);
140
- setParsedSize('right', get('right') as any);
141
- const zIndex = get('zIndex') as any;
142
- if (zIndex !== undefined)
143
- style.zIndex = zIndex as React.CSSProperties['zIndex'];
144
- return style;
145
- }
1
+ export type { ExtractViewStyleOptions } from './extractViewStyle/extractViewStyle';
2
+ export { extractViewStyle } from './extractViewStyle/extractViewStyle';
@@ -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();