@developer_tribe/react-builder 1.2.39 → 1.2.40

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 (79) hide show
  1. package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +1 -40
  2. package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +1 -1
  3. package/dist/build-components/patterns.generated.d.ts +21 -344
  4. package/dist/components/BuilderProvider.d.ts +1 -0
  5. package/dist/components/DeviceButton.d.ts +4 -1
  6. package/dist/index.cjs.js +1 -1
  7. package/dist/index.cjs.js.map +1 -1
  8. package/dist/index.esm.js +1 -1
  9. package/dist/index.esm.js.map +1 -1
  10. package/dist/index.web.cjs.js +4 -4
  11. package/dist/index.web.cjs.js.map +1 -1
  12. package/dist/index.web.esm.js +4 -4
  13. package/dist/index.web.esm.js.map +1 -1
  14. package/dist/mockOS/context/MockOSContext.d.ts +3 -1
  15. package/dist/size-matters/index.d.ts +1 -1
  16. package/dist/store.d.ts +6 -0
  17. package/dist/styles.css +1 -1
  18. package/dist/types/Device.d.ts +5 -0
  19. package/dist/utils/extractTextStyle/extractTextStyle.d.ts +1 -0
  20. package/dist/utils/extractViewStyle/extractViewStyle.d.ts +1 -0
  21. package/package.json +1 -1
  22. package/scripts/prebuild/assets/prompt_scheme.md +7 -0
  23. package/src/DeviceMockFrame.tsx +8 -0
  24. package/src/RenderPage.tsx +3 -0
  25. package/src/assets/devices.json +747 -183
  26. package/src/assets/meta.json +1 -1
  27. package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
  28. package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
  29. package/src/assets/samples/carousel-sample.json +30 -26
  30. package/src/assets/samples/paywall-1.json +30 -30
  31. package/src/assets/samples/paywall-2.json +26 -26
  32. package/src/assets/samples/paywall-app-delete-offer.json +27 -27
  33. package/src/assets/samples/paywall-app-open-offer.json +27 -27
  34. package/src/assets/samples/paywall-back-offer.json +27 -27
  35. package/src/assets/samples/paywall-notification-offer.json +27 -27
  36. package/src/assets/samples/simple-1.json +4 -4
  37. package/src/assets/samples/simple-2.json +25 -25
  38. package/src/assets/samples/unmigrated-builder-1.1.1.json +7 -7
  39. package/src/assets/samples/unmigrated-builder1.json +4 -4
  40. package/src/assets/samples/unvalidated-builder1.json +4 -4
  41. package/src/assets/samples/unvalidated-crash1.json +2 -2
  42. package/src/assets/samples/unvalidated-crashcomponent1.json +2 -2
  43. package/src/assets/samples/vpn-onboard-1.json +30 -30
  44. package/src/assets/samples/vpn-onboard-2.json +30 -30
  45. package/src/assets/samples/vpn-onboard-3.json +27 -27
  46. package/src/assets/samples/vpn-onboard-4.json +27 -27
  47. package/src/assets/samples/vpn-onboard-5.json +40 -40
  48. package/src/assets/samples/vpn-onboard-6.json +30 -30
  49. package/src/assets/samples/vpn-onboard-7.json +29 -29
  50. package/src/attribute-analyser/style/web/useExtractImageStyle.ts +8 -3
  51. package/src/attribute-analyser/style/web/useExtractViewStyle.ts +8 -3
  52. package/src/build-components/CarouselDots/CarouselDots.tsx +8 -3
  53. package/src/build-components/Main/Main.tsx +3 -1
  54. package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +15 -1
  55. package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +1 -52
  56. package/src/build-components/NavigationBarColor/pattern.json +11 -2
  57. package/src/build-components/OnboardDot/OnboardDot.tsx +3 -2
  58. package/src/build-components/PaywallCloseButton/pattern.json +1 -0
  59. package/src/build-components/StatusBarColor/StatusBarColor.tsx +15 -1
  60. package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +1 -1
  61. package/src/build-components/StatusBarColor/pattern.json +10 -1
  62. package/src/build-components/patterns.generated.ts +25 -364
  63. package/src/components/BuilderProvider.tsx +1 -0
  64. package/src/components/DeviceButton.tsx +35 -0
  65. package/src/components/EditorHeader.tsx +16 -1
  66. package/src/hooks/useSafeAreaViewStyle.ts +24 -4
  67. package/src/mockOS/context/MockOSContext.tsx +41 -13
  68. package/src/modals/DeviceSelectorModal.tsx +94 -10
  69. package/src/product-base/extractAndroidParams.ts +38 -8
  70. package/src/size-matters/index.ts +15 -9
  71. package/src/store.ts +27 -0
  72. package/src/styles/modals/_product-edit-modal.scss +2 -2
  73. package/src/types/Device.ts +5 -0
  74. package/src/utils/analyseNodeByPatterns.ts +6 -2
  75. package/src/utils/extractTextStyle/extractTextStyle.ts +3 -1
  76. package/src/utils/extractTextStyle/extractTextStyleNative.ts +1 -1
  77. package/src/utils/extractViewStyle/extractViewStyle.ts +19 -5
  78. package/src/utils/extractViewStyle/extractViewStyleNative.ts +5 -1
  79. package/src/utils/replaceLocalizationParams.ts +5 -7
@@ -1,7 +1,8 @@
1
- import React from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import { Device } from '../types/Device';
3
3
  import Modal from './Modal';
4
4
  import { DeviceButton } from '../components/DeviceButton';
5
+ import { useRenderStore } from '../store';
5
6
 
6
7
  type DeviceSelectorModalProps = {
7
8
  devices: Device[];
@@ -16,11 +17,36 @@ export function DeviceSelectorModal({
16
17
  onSelect,
17
18
  onClose,
18
19
  }: DeviceSelectorModalProps) {
20
+ const [searchTerm, setSearchTerm] = useState('');
21
+ const { favoriteDevices, toggleFavoriteDevice } = useRenderStore((state) => ({
22
+ favoriteDevices: state.favoriteDevices || [],
23
+ toggleFavoriteDevice: state.toggleFavoriteDevice,
24
+ }));
25
+
19
26
  const handleDeviceSelect = (device: Device) => {
20
27
  onSelect(device);
21
28
  onClose();
22
29
  };
23
30
 
31
+ const filteredDevices = useMemo(() => {
32
+ return devices.filter((device) =>
33
+ device.name.toLowerCase().includes(searchTerm.toLowerCase()),
34
+ );
35
+ }, [devices, searchTerm]);
36
+
37
+ const { favorites, others } = useMemo(() => {
38
+ const favs: Device[] = [];
39
+ const rest: Device[] = [];
40
+ filteredDevices.forEach((device) => {
41
+ if (favoriteDevices.includes(device.name)) {
42
+ favs.push(device);
43
+ } else {
44
+ rest.push(device);
45
+ }
46
+ });
47
+ return { favorites: favs, others: rest };
48
+ }, [filteredDevices, favoriteDevices]);
49
+
24
50
  return (
25
51
  <Modal
26
52
  onClose={onClose}
@@ -40,15 +66,73 @@ export function DeviceSelectorModal({
40
66
  Close
41
67
  </button>
42
68
  </div>
43
- <div className="device-selector-modal__grid" role="list">
44
- {devices.map((device) => (
45
- <DeviceButton
46
- key={device.name}
47
- device={device}
48
- selectedDevice={selectedDevice}
49
- onSelect={handleDeviceSelect}
50
- />
51
- ))}
69
+ <div
70
+ className="device-selector-modal__search"
71
+ style={{ padding: '0 16px', marginBottom: '8px' }}
72
+ >
73
+ <input
74
+ type="text"
75
+ placeholder="Search devices..."
76
+ value={searchTerm}
77
+ onChange={(e) => setSearchTerm(e.target.value)}
78
+ className="editor-input"
79
+ style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
80
+ />
81
+ </div>
82
+ <div
83
+ className="device-selector-modal__body"
84
+ style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}
85
+ >
86
+ {favorites.length > 0 && (
87
+ <section>
88
+ <h4
89
+ style={{
90
+ margin: '0 16px 8px',
91
+ fontSize: '14px',
92
+ fontWeight: 'bold',
93
+ }}
94
+ >
95
+ Favorites
96
+ </h4>
97
+ <div className="device-selector-modal__grid" role="list">
98
+ {favorites.map((device) => (
99
+ <DeviceButton
100
+ key={`fav-${device.name}`}
101
+ device={device}
102
+ selectedDevice={selectedDevice}
103
+ onSelect={handleDeviceSelect}
104
+ isFavorite={true}
105
+ onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
106
+ />
107
+ ))}
108
+ </div>
109
+ </section>
110
+ )}
111
+ <section>
112
+ {favorites.length > 0 && (
113
+ <h4
114
+ style={{
115
+ margin: '0 16px 8px',
116
+ fontSize: '14px',
117
+ fontWeight: 'bold',
118
+ }}
119
+ >
120
+ All Devices
121
+ </h4>
122
+ )}
123
+ <div className="device-selector-modal__grid" role="list">
124
+ {others.map((device) => (
125
+ <DeviceButton
126
+ key={`other-${device.name}`}
127
+ device={device}
128
+ selectedDevice={selectedDevice}
129
+ onSelect={handleDeviceSelect}
130
+ isFavorite={false}
131
+ onToggleFavorite={(d) => toggleFavoriteDevice(d.name)}
132
+ />
133
+ ))}
134
+ </div>
135
+ </section>
52
136
  </div>
53
137
  </Modal>
54
138
  );
@@ -70,7 +70,7 @@ export function extractAndroidParams(
70
70
  iapLogger.warn(['extractAndroidParams'], 'No offers found in product', {
71
71
  productId: product.id || product.productId,
72
72
  });
73
- return getEmptyParams();
73
+ return getFallbackParams(product);
74
74
  }
75
75
 
76
76
  const pricingPhases =
@@ -81,7 +81,7 @@ export function extractAndroidParams(
81
81
  productId: product.id || product.productId,
82
82
  offerId: selectedOffer.id,
83
83
  });
84
- return getEmptyParams();
84
+ return getFallbackParams(product);
85
85
  }
86
86
 
87
87
  // Trial phase: priceAmountMicros === "0"
@@ -105,7 +105,7 @@ export function extractAndroidParams(
105
105
  productId: product.id || product.productId,
106
106
  pricingPhasesCount: pricingPhases.length,
107
107
  });
108
- return getEmptyParams();
108
+ return getFallbackParams(product);
109
109
  }
110
110
 
111
111
  const regularPeriod = parseBillingPeriod(regularPhase.billingPeriod);
@@ -142,15 +142,31 @@ export function extractAndroidParams(
142
142
  }
143
143
 
144
144
  const discountPercentage = calculateDiscount(price, promoPrice);
145
- const priceNum = parseFloat(price);
145
+
146
+ // Apply root properties if they exist (allows mock edits to immediately bypass deep structures)
147
+ const finalPrice =
148
+ typeof product.price === 'string' && product.price !== ''
149
+ ? product.price
150
+ : price;
151
+ const finalCurrency =
152
+ typeof product.currency === 'string' && product.currency !== ''
153
+ ? product.currency
154
+ : currency;
155
+ const finalLocalizedPrice =
156
+ typeof product.localizedPrice === 'string' &&
157
+ product.localizedPrice !== ''
158
+ ? product.localizedPrice
159
+ : localizedPrice;
160
+
161
+ const priceNum = parseFloat(finalPrice);
146
162
  const pricePerMonth = calculatePricePerMonth(priceNum, regularPeriod.unit);
147
163
  const pricePerYear = calculatePricePerYear(priceNum, regularPeriod.unit);
148
164
 
149
165
  return {
150
- price,
166
+ price: finalPrice,
151
167
  promoPrice,
152
- currency,
153
- localizedPrice,
168
+ currency: finalCurrency,
169
+ localizedPrice: finalLocalizedPrice,
154
170
  period: regularPeriod.unit,
155
171
  periodValue: String(regularPeriod.value),
156
172
  periodType,
@@ -173,10 +189,24 @@ export function extractAndroidParams(
173
189
  error: error instanceof Error ? error.message : String(error),
174
190
  },
175
191
  );
176
- return getEmptyParams();
192
+ return getFallbackParams(product);
177
193
  }
178
194
  }
179
195
 
196
+ /** Boş params ama mock product price'ları ile birleştirilmiş (fallback) */
197
+ function getFallbackParams(product: Product): AndroidParams {
198
+ const price = String(product.price || product.localizedPrice || '').replace(
199
+ /[^0-9.]/g,
200
+ '',
201
+ );
202
+ return {
203
+ ...getEmptyParams(),
204
+ price,
205
+ currency: product.currency || product.currencyCode || '',
206
+ localizedPrice: product.localizedPrice || '',
207
+ };
208
+ }
209
+
180
210
  /** Boş params döner (fallback) */
181
211
  function getEmptyParams(): AndroidParams {
182
212
  return {
@@ -52,15 +52,21 @@ export const ms = (size: number, factor?: number) =>
52
52
 
53
53
  export function parseSize(
54
54
  value: string | number | undefined,
55
- baseSize?: BaseSize,
56
- device?: Device,
55
+ baseSize: BaseSize | undefined,
56
+ device: Device | 'native' | undefined,
57
57
  ) {
58
58
  if (value === undefined) return undefined;
59
+
60
+ const multiplier = device === 'native' ? 1 : (device?.multiplier ?? 1);
61
+
59
62
  if (typeof value === 'number') {
60
- return value;
63
+ return value * multiplier;
61
64
  }
62
65
 
63
- const scalers = getScalers(baseSize, device);
66
+ const scalers = getScalers(
67
+ baseSize,
68
+ device === 'native' ? undefined : device,
69
+ );
64
70
 
65
71
  const raw = String(value).trim();
66
72
  const lower = raw.toLowerCase();
@@ -68,16 +74,16 @@ export function parseSize(
68
74
  // Handle explicit scalers via suffixes
69
75
  if (lower.endsWith('@s')) {
70
76
  const n = parseFloat(lower.slice(0, -2));
71
- return Number.isFinite(n) ? scalers.scale(n) : raw;
77
+ return Number.isFinite(n) ? scalers.scale(n) * multiplier : raw;
72
78
  }
73
79
  if (lower.endsWith('@vs')) {
74
80
  const n = parseFloat(lower.slice(0, -3));
75
- return Number.isFinite(n) ? scalers.verticalScale(n) : raw;
81
+ return Number.isFinite(n) ? scalers.verticalScale(n) * multiplier : raw;
76
82
  }
77
83
  if (lower.endsWith('@f') || lower.endsWith('@fs')) {
78
84
  const cut = lower.endsWith('@f') ? -2 : -3;
79
85
  const n = parseFloat(lower.slice(0, cut));
80
- return Number.isFinite(n) ? scalers.verticalScale(n) : raw;
86
+ return Number.isFinite(n) ? scalers.verticalScale(n) * multiplier : raw;
81
87
  }
82
88
 
83
89
  // Preserve percentage values as-is
@@ -89,13 +95,13 @@ export function parseSize(
89
95
  // Handle px explicitly (treat as absolute, not scaled)
90
96
  if (lower.endsWith('px')) {
91
97
  const n = parseFloat(lower.replace('px', ''));
92
- return Number.isFinite(n) ? n : raw;
98
+ return Number.isFinite(n) ? n * multiplier : raw;
93
99
  }
94
100
 
95
101
  // Plain numeric strings fall back to provided scaler (implicit number)
96
102
  const numeric = parseFloat(lower);
97
103
  if (Number.isFinite(numeric)) {
98
- return numeric;
104
+ return numeric * multiplier;
99
105
  }
100
106
 
101
107
  // Unknown format, return as-is
package/src/store.ts CHANGED
@@ -92,8 +92,15 @@ type RenderStore = {
92
92
  // OS bar color overrides (set by StatusBarColor / NavigationBarColor build components)
93
93
  statusBarOverrideColor: string | null;
94
94
  setStatusBarOverrideColor: (color: string | null) => void;
95
+ statusBarOverrideTranslucent: boolean | null;
96
+ setStatusBarOverrideTranslucent: (translucent: boolean | null) => void;
95
97
  navBarOverrideColor: string | null;
96
98
  setNavBarOverrideColor: (color: string | null) => void;
99
+ navBarOverrideTranslucent: boolean | null;
100
+ setNavBarOverrideTranslucent: (translucent: boolean | null) => void;
101
+
102
+ favoriteDevices: string[];
103
+ toggleFavoriteDevice: (name: string) => void;
97
104
  };
98
105
 
99
106
  export const useRenderStore = createWithEqualityFn<RenderStore>()(
@@ -269,8 +276,26 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
269
276
  statusBarOverrideColor: null,
270
277
  setStatusBarOverrideColor: (color) =>
271
278
  set({ statusBarOverrideColor: color }),
279
+ statusBarOverrideTranslucent: null,
280
+ setStatusBarOverrideTranslucent: (translucent) =>
281
+ set({ statusBarOverrideTranslucent: translucent }),
272
282
  navBarOverrideColor: null,
273
283
  setNavBarOverrideColor: (color) => set({ navBarOverrideColor: color }),
284
+ navBarOverrideTranslucent: null,
285
+ setNavBarOverrideTranslucent: (translucent) =>
286
+ set({ navBarOverrideTranslucent: translucent }),
287
+
288
+ favoriteDevices: [],
289
+ toggleFavoriteDevice: (name) =>
290
+ set((state) => {
291
+ const devs = state.favoriteDevices || [];
292
+ const isFav = devs.includes(name);
293
+ return {
294
+ favoriteDevices: isFav
295
+ ? devs.filter((n) => n !== name)
296
+ : [...devs, name],
297
+ };
298
+ }),
274
299
  }),
275
300
  {
276
301
  name: 'render-store',
@@ -281,6 +306,8 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
281
306
  products: state.products,
282
307
  benefits: state.benefits,
283
308
  listMaxNested: state.listMaxNested,
309
+ favoriteDevices: state.favoriteDevices,
310
+ device: state.device,
284
311
  }),
285
312
  storage: createJSONStorage(() => localStorage),
286
313
  },
@@ -1,7 +1,7 @@
1
1
  @use '../foundation/sizes' as sizes;
2
2
 
3
3
  .product-edit-modal {
4
- width: min(640px, calc(100vw - 32px));
4
+ width: min(800px, calc(100vw - 32px));
5
5
  max-height: 90vh;
6
6
  padding: sizes.$spaceRoomy;
7
7
  display: flex;
@@ -17,7 +17,7 @@
17
17
  }
18
18
 
19
19
  .product-edit-modal__textarea {
20
- min-height: 90px;
20
+ min-height: 140px;
21
21
  padding: 8px;
22
22
  resize: vertical;
23
23
  }
@@ -35,4 +35,9 @@ export interface Device {
35
35
  * 1 = highest importance, 100 = lowest importance
36
36
  */
37
37
  importance?: number;
38
+ /**
39
+ * Optional multiplier to adjust for differences in CSS viewport sizing between web and native.
40
+ * Example: 0.5 for a device where web renders it twice as large as expected compared to native.
41
+ */
42
+ multiplier?: number;
38
43
  }
@@ -429,11 +429,15 @@ function validateAttributesByPattern(
429
429
  if (attrName === 'style' || attrName === 'styles') continue;
430
430
  const attrSpec = schema?.[attrName] as AttributeTypeSpec | undefined;
431
431
  if (!attrSpec) {
432
- if (attrName === 'title' || attrName === 'description') {
432
+ if (
433
+ attrName === 'title' ||
434
+ attrName === 'description' ||
435
+ attrName === 'testID'
436
+ ) {
433
437
  const res = validateAttributeValue(
434
438
  pattern.pattern.type,
435
439
  attrValue,
436
- attrName,
440
+ attrName === 'testID' ? 'string' : attrName,
437
441
  joinPath(path, attrName),
438
442
  );
439
443
  if (!res.valid) return res;
@@ -95,6 +95,7 @@ export type ExtractTextStyleOptions = {
95
95
  onError?: (error: string) => void;
96
96
  directlyTextStyle?: boolean;
97
97
  baseSize?: BaseSize;
98
+ device?: import('../../types/Device').Device;
98
99
  };
99
100
 
100
101
  export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
@@ -127,7 +128,7 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
127
128
  // Typography
128
129
  const fontSize = get('fontSize') as string | number | undefined;
129
130
  if (fontSize !== undefined) {
130
- const parsed = parseSize(fontSize, options.baseSize);
131
+ const parsed = parseSize(fontSize, options.baseSize, options.device!);
131
132
  style.fontSize = parsed as React.CSSProperties['fontSize'];
132
133
  } else {
133
134
  style.fontSize = fs(14);
@@ -194,6 +195,7 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
194
195
  projectColors: options.projectColors,
195
196
  theme,
196
197
  baseSize: options.baseSize,
198
+ device: options.device,
197
199
  });
198
200
  const fullStyle = { ...viewStyle, ...style };
199
201
 
@@ -63,7 +63,7 @@ export function extractTextStyleNative<
63
63
  }
64
64
 
65
65
  const rawFontSize = get('fontSize') as string | number | undefined;
66
- const parsedFontSize = parseSize(rawFontSize, options.baseSize);
66
+ const parsedFontSize = parseSize(rawFontSize, options.baseSize, 'native');
67
67
  if (typeof parsedFontSize === 'number') style.fontSize = parsedFontSize;
68
68
  else style.fontSize = fs(14);
69
69
 
@@ -8,11 +8,13 @@ import type { BaseSize } from '../../types/PreviewConfig';
8
8
  import { parseSize } from '../../size-matters';
9
9
  import { parseColor } from '../parseColor';
10
10
  import { getStyleBag, toAttributeRecord } from '../attributeStyle';
11
+ import { getDefaultDevice } from '../getDevices';
11
12
 
12
13
  export type ExtractViewStyleOptions = {
13
14
  projectColors?: ProjectColors;
14
15
  theme?: string;
15
16
  baseSize?: BaseSize;
17
+ device?: import('../../types/Device').Device;
16
18
  };
17
19
 
18
20
  export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
@@ -74,7 +76,7 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
74
76
  rawValue: string | number | undefined,
75
77
  ) => {
76
78
  if (isEmptySizeValue(rawValue)) return;
77
- const parsed = parseSize(rawValue, options.baseSize);
79
+ const parsed = parseSize(rawValue, options.baseSize, options.device!);
78
80
  style[property] = parsed as React.CSSProperties[K];
79
81
  };
80
82
 
@@ -87,13 +89,21 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
87
89
  | number
88
90
  | undefined;
89
91
  if (!isEmptySizeValue(paddingHorizontal)) {
90
- const parsed = parseSize(paddingHorizontal, options.baseSize);
92
+ const parsed = parseSize(
93
+ paddingHorizontal,
94
+ options.baseSize,
95
+ options.device!,
96
+ );
91
97
  style.paddingLeft = parsed as React.CSSProperties['paddingLeft'];
92
98
  style.paddingRight = parsed as React.CSSProperties['paddingRight'];
93
99
  }
94
100
  const paddingVertical = get('paddingVertical') as string | number | undefined;
95
101
  if (!isEmptySizeValue(paddingVertical)) {
96
- const parsed = parseSize(paddingVertical, options.baseSize);
102
+ const parsed = parseSize(
103
+ paddingVertical,
104
+ options.baseSize,
105
+ options.device!,
106
+ );
97
107
  style.paddingTop = parsed as React.CSSProperties['paddingTop'];
98
108
  style.paddingBottom = parsed as React.CSSProperties['paddingBottom'];
99
109
  }
@@ -117,14 +127,18 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
117
127
  (attrRecord.marginHorizontal as string | number | undefined) ??
118
128
  (styleBag?.marginHorizontal as string | number | undefined);
119
129
  if (!isEmptySizeValue(marginHorizontalRaw)) {
120
- const parsed = parseSize(marginHorizontalRaw, options.baseSize);
130
+ const parsed = parseSize(
131
+ marginHorizontalRaw,
132
+ options.baseSize,
133
+ options.device!,
134
+ );
121
135
  style.marginLeft = parsed as React.CSSProperties['marginLeft'];
122
136
  style.marginRight = parsed as React.CSSProperties['marginRight'];
123
137
  }
124
138
 
125
139
  const marginVertical = get('marginVertical') as string | number | undefined;
126
140
  if (!isEmptySizeValue(marginVertical)) {
127
- const parsed = parseSize(marginVertical, options.baseSize);
141
+ const parsed = parseSize(marginVertical, options.baseSize, options.device!);
128
142
  style.marginTop = parsed as React.CSSProperties['marginTop'];
129
143
  style.marginBottom = parsed as React.CSSProperties['marginBottom'];
130
144
  }
@@ -51,7 +51,11 @@ export function extractViewStyleNative<
51
51
 
52
52
  const setParsedSize = (property: string, rawValue: unknown) => {
53
53
  if (isEmptySizeValue(rawValue)) return;
54
- const parsed = parseSize(rawValue as string | number, options.baseSize);
54
+ const parsed = parseSize(
55
+ rawValue as string | number,
56
+ options.baseSize,
57
+ 'native',
58
+ );
55
59
  // RN generally expects numbers (dp). We allow percentages for width/height-like props.
56
60
  if (typeof parsed === 'number' || typeof parsed === 'string') {
57
61
  style[property] = parsed;
@@ -9,11 +9,9 @@ export function replaceLocalizationParams(
9
9
  // (e.g. @trialPeriod matching inside @trialPeriodUnit)
10
10
  const sorted = Object.entries(params).sort(([a], [b]) => b.length - a.length);
11
11
 
12
- return (
13
- sorted.reduce((acc, [key, val]) => {
14
- if (val == null) return acc;
15
- const needle = `@${key}`;
16
- return acc.split(needle).join(val);
17
- }, text) || text
18
- );
12
+ return sorted.reduce((acc, [key, val]) => {
13
+ if (val == null) return acc;
14
+ const needle = `@${key}`;
15
+ return acc.split(needle).join(val);
16
+ }, text);
19
17
  }