@developer_tribe/react-builder 1.2.23 → 1.2.25

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 (150) hide show
  1. package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +5 -5
  2. package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +6 -4
  3. package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +5 -3
  4. package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
  5. package/dist/attributes-editor/attributesEditorModelTypes.d.ts +2 -0
  6. package/dist/build-components/BIcon/BIconProps.generated.d.ts +0 -2
  7. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +0 -2
  8. package/dist/build-components/Button/ButtonProps.generated.d.ts +0 -2
  9. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +0 -2
  10. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +0 -2
  11. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +0 -2
  12. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +0 -2
  13. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +0 -2
  14. package/dist/build-components/CountDown/CountDownProps.generated.d.ts +0 -2
  15. package/dist/build-components/Counter/CounterProps.generated.d.ts +0 -2
  16. package/dist/build-components/Image/ImageProps.generated.d.ts +0 -2
  17. package/dist/build-components/Main/MainProps.generated.d.ts +0 -2
  18. package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +0 -2
  19. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +0 -2
  20. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +0 -2
  21. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +0 -2
  22. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +0 -2
  23. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +0 -2
  24. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +0 -2
  25. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +0 -2
  26. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +0 -2
  27. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +0 -2
  28. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +0 -2
  29. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +0 -2
  30. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +0 -2
  31. package/dist/build-components/PaywallCounter/PaywallCounterProps.generated.d.ts +0 -2
  32. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +0 -2
  33. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +0 -2
  34. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +0 -2
  35. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +0 -2
  36. package/dist/build-components/Separator/SeparatorProps.generated.d.ts +0 -2
  37. package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +0 -2
  38. package/dist/build-components/Text/TextProps.generated.d.ts +0 -2
  39. package/dist/build-components/patterns.generated.d.ts +80 -66
  40. package/dist/index.cjs.js +2 -2
  41. package/dist/index.cjs.js.map +1 -1
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.esm.js +2 -2
  44. package/dist/index.esm.js.map +1 -1
  45. package/dist/index.web.cjs.js +3 -3
  46. package/dist/index.web.cjs.js.map +1 -1
  47. package/dist/index.web.esm.js +3 -3
  48. package/dist/index.web.esm.js.map +1 -1
  49. package/dist/pages/ProjectPage.d.ts +2 -2
  50. package/dist/pages/projectPageUtils.d.ts +7 -1
  51. package/dist/types/Project.d.ts +6 -0
  52. package/dist/utils/attributeStyle.d.ts +12 -0
  53. package/dist/utils/patterns.d.ts +2 -0
  54. package/package.json +6 -1
  55. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +11 -2
  56. package/src/AttributesEditor.tsx +15 -4
  57. package/src/assets/meta.json +1 -1
  58. package/src/assets/samples/paywall-1.json +5 -5
  59. package/src/assets/samples/paywall-2.json +5 -5
  60. package/src/assets/samples/paywall-app-delete-offer.json +0 -1
  61. package/src/assets/samples/paywall-app-open-offer.json +0 -1
  62. package/src/assets/samples/paywall-back-offer.json +0 -1
  63. package/src/assets/samples/paywall-notification-offer.json +0 -1
  64. package/src/assets/samples/simple-2.json +0 -1
  65. package/src/attribute-analyser/style/native/useExtractImageStyle.ts +19 -15
  66. package/src/attribute-analyser/style/native/useExtractTextStyle.ts +25 -15
  67. package/src/attribute-analyser/style/native/useExtractViewStyle.ts +19 -21
  68. package/src/attributes-editor/AttributesEditorView.tsx +43 -36
  69. package/src/attributes-editor/SpecialCategorySection.tsx +5 -3
  70. package/src/attributes-editor/attributesEditorModelTypes.ts +2 -0
  71. package/src/attributes-editor/useAttributesEditorModel.ts +6 -0
  72. package/src/build-components/BIcon/BIconProps.generated.ts +0 -2
  73. package/src/build-components/BIcon/pattern.json +5 -3
  74. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +0 -2
  75. package/src/build-components/BackgroundImage/pattern.json +12 -4
  76. package/src/build-components/Button/ButtonProps.generated.ts +0 -2
  77. package/src/build-components/Button/pattern.json +5 -3
  78. package/src/build-components/Carousel/CarouselProps.generated.ts +0 -2
  79. package/src/build-components/Carousel/pattern.json +11 -5
  80. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +0 -2
  81. package/src/build-components/CarouselButtons/pattern.json +11 -4
  82. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +0 -2
  83. package/src/build-components/CarouselDots/pattern.json +5 -3
  84. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +0 -2
  85. package/src/build-components/CarouselItem/pattern.json +6 -6
  86. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +0 -2
  87. package/src/build-components/CarouselProvider/pattern.json +6 -5
  88. package/src/build-components/CountDown/CountDownProps.generated.ts +0 -2
  89. package/src/build-components/CountDown/pattern.json +6 -3
  90. package/src/build-components/Counter/CounterProps.generated.ts +0 -2
  91. package/src/build-components/Counter/pattern.json +5 -1
  92. package/src/build-components/Image/ImageProps.generated.ts +0 -2
  93. package/src/build-components/Image/pattern.json +7 -2
  94. package/src/build-components/Main/MainProps.generated.ts +0 -2
  95. package/src/build-components/Main/pattern.json +5 -3
  96. package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +0 -2
  97. package/src/build-components/NavigationBarColor/pattern.json +5 -3
  98. package/src/build-components/Onboard/OnboardProps.generated.ts +0 -2
  99. package/src/build-components/Onboard/pattern.json +9 -7
  100. package/src/build-components/OnboardButton/OnboardButton.tsx +19 -5
  101. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +0 -2
  102. package/src/build-components/OnboardButton/pattern.json +16 -5
  103. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +0 -2
  104. package/src/build-components/OnboardButtons/pattern.json +17 -6
  105. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +0 -2
  106. package/src/build-components/OnboardDot/pattern.json +5 -3
  107. package/src/build-components/OnboardFooter/OnboardFooter.tsx +15 -4
  108. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +0 -2
  109. package/src/build-components/OnboardFooter/pattern.json +5 -3
  110. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +0 -2
  111. package/src/build-components/OnboardImage/pattern.json +7 -3
  112. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +0 -2
  113. package/src/build-components/OnboardItem/pattern.json +13 -5
  114. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +0 -2
  115. package/src/build-components/OnboardProvider/pattern.json +10 -4
  116. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +0 -2
  117. package/src/build-components/OnboardSubtitle/pattern.json +7 -6
  118. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +0 -2
  119. package/src/build-components/OnboardTitle/pattern.json +7 -6
  120. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +0 -2
  121. package/src/build-components/PaywallBackground/pattern.json +5 -5
  122. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +0 -2
  123. package/src/build-components/PaywallCloseButton/pattern.json +6 -6
  124. package/src/build-components/PaywallCounter/PaywallCounterProps.generated.ts +0 -2
  125. package/src/build-components/PaywallCounter/pattern.json +6 -3
  126. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +0 -2
  127. package/src/build-components/PaywallOptions/pattern.json +6 -6
  128. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +0 -2
  129. package/src/build-components/PaywallProvider/pattern.json +5 -3
  130. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +0 -2
  131. package/src/build-components/PaywallSubscribeButton/pattern.json +6 -6
  132. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +0 -2
  133. package/src/build-components/RadioButton/pattern.json +5 -3
  134. package/src/build-components/Separator/SeparatorProps.generated.ts +0 -2
  135. package/src/build-components/Separator/pattern.json +5 -3
  136. package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +0 -2
  137. package/src/build-components/StatusBarColor/pattern.json +5 -3
  138. package/src/build-components/Text/TextProps.generated.ts +0 -2
  139. package/src/build-components/Text/pattern.json +11 -5
  140. package/src/build-components/View/pattern.json +18 -4
  141. package/src/build-components/patterns.generated.ts +72 -66
  142. package/src/components/AttributesEditorPanel.tsx +48 -32
  143. package/src/components/Builder.tsx +4 -1
  144. package/src/components/BuilderProvider.tsx +6 -6
  145. package/src/index.ts +4 -1
  146. package/src/pages/ProjectPage.tsx +45 -22
  147. package/src/pages/projectPageUtils.ts +15 -1
  148. package/src/types/Project.ts +7 -0
  149. package/src/utils/attributeStyle.ts +78 -0
  150. package/src/utils/patterns.ts +2 -0
@@ -1,10 +1,10 @@
1
- import type { Project, ProjectColors } from '../types/Project';
1
+ import type { Project, ProjectColors, ProjectMeta } from '../types/Project';
2
2
  import { AppConfig } from '../types/PreviewConfig';
3
3
  import type { LogLevel } from '../types/Project';
4
4
  import type { Fonts } from '../types/Fonts';
5
5
  export type ProjectPageProps = {
6
6
  project: Project;
7
- onSaveProject: (project: Project) => void;
7
+ onSaveProject: (project: ProjectMeta) => void;
8
8
  appConfig?: AppConfig;
9
9
  logLevel?: LogLevel;
10
10
  projectColors?: ProjectColors;
@@ -1,7 +1,13 @@
1
- import type { Project } from '../types/Project';
1
+ import type { Project, ProjectMeta } from '../types/Project';
2
2
  import type { Node } from '../types/Node';
3
3
  export declare function resolveProjectForSave(args: {
4
4
  project: Project;
5
5
  overrideProject?: Project | null;
6
6
  data: Node;
7
7
  }): Project;
8
+ /**
9
+ * Strips a full Project down to its essential persistence fields.
10
+ * Use before handing the project to onSaveProject so consumers only
11
+ * receive the canonical metadata (name, version, type, data).
12
+ */
13
+ export declare function toProjectMeta(project: Project): ProjectMeta;
@@ -24,6 +24,12 @@ export interface ProjectBase<T> {
24
24
  }
25
25
  export interface Project extends ProjectBase<Node> {
26
26
  }
27
+ /**
28
+ * Lightweight subset of Project containing only the essential metadata
29
+ * needed for persistence (name, version, type, data).
30
+ * Excludes runtime/editor-only fields like appConfig and projectColors.
31
+ */
32
+ export type ProjectMeta = Pick<Project, 'name' | 'version' | 'type' | 'data'>;
27
33
  export type LogLevel = 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE';
28
34
  export type LogSource = string;
29
35
  export interface LogEntry {
@@ -7,3 +7,15 @@ import type { NodeDefaultAttribute } from '../types/Node';
7
7
  export declare function getStyleBag(attributes: NodeDefaultAttribute | undefined): Record<string, unknown> | undefined;
8
8
  /** Safe indexed access to attributes. Use for reading style/direct props. */
9
9
  export declare function toAttributeRecord(attributes: unknown): Record<string, unknown>;
10
+ /**
11
+ * All attribute keys that represent visual style properties.
12
+ * Used to separate style keys from non-style (behavioral/content) keys.
13
+ *
14
+ * Keep in sync with ViewStyleGenerated, TextStyleGenerated, and ImageStyleGenerated.
15
+ */
16
+ declare const STYLE_ATTR_KEYS_LIST: readonly ["style", "styles", "flexDirection", "flexWrap", "alignItems", "justifyContent", "gap", "padding", "paddingHorizontal", "paddingVertical", "paddingTop", "paddingBottom", "paddingLeft", "paddingRight", "margin", "marginHorizontal", "marginVertical", "marginTop", "marginBottom", "marginLeft", "marginRight", "backgroundColor", "borderRadius", "width", "minWidth", "maxWidth", "height", "minHeight", "maxHeight", "flex", "position", "top", "bottom", "left", "right", "zIndex", "color", "fontSize", "fontFamily", "fontWeight", "textAlign", "resizeMode"];
17
+ /** Type-level union of all style attribute keys. Use with `Omit<T, StyleAttrKey>`. */
18
+ export type StyleAttrKey = (typeof STYLE_ATTR_KEYS_LIST)[number];
19
+ /** Strips all visual-style keys from an attributes record, returning only non-style keys. */
20
+ export declare function stripStyleKeys(attrs: Record<string, unknown>): Record<string, unknown>;
21
+ export {};
@@ -41,6 +41,8 @@ type Pattern = {
41
41
  schemaVersion: number;
42
42
  pattern: {
43
43
  type: string;
44
+ title?: string;
45
+ description?: string;
44
46
  children: unknown;
45
47
  attributes?: Record<string, string | string[]>;
46
48
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@developer_tribe/react-builder",
3
- "version": "1.2.23",
3
+ "version": "1.2.25",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "restricted": true,
@@ -73,6 +73,7 @@
73
73
  "lottie-react": "^2.4.1",
74
74
  "prettier": "^3.6.2",
75
75
  "react": "^18.3.1",
76
+ "react-native": "^0.83.1",
76
77
  "rimraf": "^6.0.1",
77
78
  "rollup": "^4.52.2",
78
79
  "rollup-plugin-peer-deps-external": "^2.2.4",
@@ -90,12 +91,16 @@
90
91
  "peerDependencies": {
91
92
  "react": ">=17",
92
93
  "react-dom": ">=17",
94
+ "react-native": ">=0.70",
93
95
  "react-router-dom": ">=6.0.0"
94
96
  },
95
97
  "peerDependenciesMeta": {
96
98
  "react-dom": {
97
99
  "optional": true
98
100
  },
101
+ "react-native": {
102
+ "optional": true
103
+ },
99
104
  "react-router-dom": {
100
105
  "optional": true
101
106
  }
@@ -120,11 +120,20 @@ async function validatePatternJson(componentDir, componentName) {
120
120
  `[${componentName}] pattern.json -> 'pattern.children' must be one of: node | string | never`
121
121
  );
122
122
  }
123
- if (typeof pattern.attributes !== 'object' || pattern.attributes == null) {
123
+ // pattern.attributes is optional components that only declare title/description
124
+ // (now at pattern level) may have no extra attributes at all.
125
+ if (
126
+ Object.prototype.hasOwnProperty.call(pattern, 'attributes') &&
127
+ (typeof pattern.attributes !== 'object' || pattern.attributes == null)
128
+ ) {
124
129
  return fail(
125
- `[${componentName}] pattern.json -> 'pattern.attributes' must be an object`
130
+ `[${componentName}] pattern.json -> 'pattern.attributes' must be an object (or omitted)`
126
131
  );
127
132
  }
133
+ // Normalise missing attributes to empty object for the rest of the validation
134
+ if (!pattern.attributes) {
135
+ pattern.attributes = {};
136
+ }
128
137
 
129
138
  // Helpers for validating custom types
130
139
  const isPrimitive = t =>
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
2
  import type { AttributesEditorProps } from './attributes-editor/attributesEditorModelTypes';
3
3
  import { useLogRender } from './utils/useLogRender';
4
4
  import { AttributesEditorView } from './attributes-editor/AttributesEditorView';
@@ -8,11 +8,22 @@ export function AttributesEditor(props: AttributesEditorProps) {
8
8
  useLogRender('AttributesEditor');
9
9
  const model = useAttributesEditorModel(props);
10
10
  const titleValue = model.attributes?.title;
11
+ const hasTitleField = model.hasTitleField;
12
+
13
+ // Keep a stable ref so the effect doesn't re-run when the callback
14
+ // reference changes (which happens every render due to unstable onChange prop).
15
+ const handleAttributeChangeRef = useRef(model.handleAttributeChange);
16
+ handleAttributeChangeRef.current = model.handleAttributeChange;
17
+
11
18
  useEffect(() => {
12
- if (typeof titleValue === 'string' && titleValue.length > 20) {
13
- model.handleAttributeChange('title', titleValue.slice(0, 20));
19
+ if (
20
+ hasTitleField &&
21
+ typeof titleValue === 'string' &&
22
+ titleValue.length > 20
23
+ ) {
24
+ handleAttributeChangeRef.current('title', titleValue.slice(0, 20));
14
25
  }
15
- }, [titleValue, model.handleAttributeChange]);
26
+ }, [hasTitleField, titleValue]);
16
27
  return <AttributesEditorView {...model} />;
17
28
  }
18
29
 
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "supportedProjectVersion": "1.1.2",
3
- "reactBuilderVersion": "1.2.22"
3
+ "reactBuilderVersion": "1.2.24"
4
4
  }
@@ -146,7 +146,7 @@
146
146
  "children": null
147
147
  },
148
148
  {
149
- "type": "text",
149
+ "type": "Text",
150
150
  "attributes": {
151
151
  "description": "Metin öğesi. (#1)",
152
152
  "title": "text 1",
@@ -190,7 +190,7 @@
190
190
  "children": null
191
191
  },
192
192
  {
193
- "type": "text",
193
+ "type": "Text",
194
194
  "attributes": {
195
195
  "description": "Metin öğesi. (#2)",
196
196
  "title": "text 2",
@@ -234,7 +234,7 @@
234
234
  "children": null
235
235
  },
236
236
  {
237
- "type": "text",
237
+ "type": "Text",
238
238
  "attributes": {
239
239
  "description": "Metin öğesi. (#3)",
240
240
  "title": "text 3",
@@ -273,7 +273,7 @@
273
273
  "children": null
274
274
  },
275
275
  {
276
- "type": "text",
276
+ "type": "Text",
277
277
  "attributes": {
278
278
  "description": "Metin öğesi. (#4)",
279
279
  "title": "Product Desc(s)",
@@ -284,7 +284,7 @@
284
284
  "children": "@productDescription — Unlock all premium features for a month."
285
285
  },
286
286
  {
287
- "type": "text",
287
+ "type": "Text",
288
288
  "attributes": {
289
289
  "description": "Metin öğesi. (#5)",
290
290
  "title": "Product Price(s)",
@@ -144,7 +144,7 @@
144
144
  "children": null
145
145
  },
146
146
  {
147
- "type": "text",
147
+ "type": "Text",
148
148
  "attributes": {
149
149
  "description": "Text item.",
150
150
  "title": "text 1",
@@ -188,7 +188,7 @@
188
188
  "children": null
189
189
  },
190
190
  {
191
- "type": "text",
191
+ "type": "Text",
192
192
  "attributes": {
193
193
  "description": "Text item.",
194
194
  "title": "text 2",
@@ -232,7 +232,7 @@
232
232
  "children": null
233
233
  },
234
234
  {
235
- "type": "text",
235
+ "type": "Text",
236
236
  "attributes": {
237
237
  "description": "Text item.",
238
238
  "title": "text 3",
@@ -271,7 +271,7 @@
271
271
  "children": null
272
272
  },
273
273
  {
274
- "type": "text",
274
+ "type": "Text",
275
275
  "attributes": {
276
276
  "description": "Product description.",
277
277
  "title": "Product Desc(s)",
@@ -282,7 +282,7 @@
282
282
  "children": "@productDescription — Unlock all premium features for a month."
283
283
  },
284
284
  {
285
- "type": "text",
285
+ "type": "Text",
286
286
  "attributes": {
287
287
  "description": "Product price.",
288
288
  "title": "Product Price(s)",
@@ -68,7 +68,6 @@
68
68
  "type": "PaywallBackground",
69
69
  "attributes": {
70
70
  "src": "https://images.unsplash.com/photo-1496307042754-b4aa456c4a2d?auto=format&fit=crop&w=1200&q=80",
71
- "resizeMode": "cover",
72
71
  "description": "Paywall background.",
73
72
  "title": "Paywall Background"
74
73
  },
@@ -68,7 +68,6 @@
68
68
  "type": "PaywallBackground",
69
69
  "attributes": {
70
70
  "src": "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=1200&q=80",
71
- "resizeMode": "cover",
72
71
  "description": "Paywall background.",
73
72
  "title": "Paywall Background"
74
73
  },
@@ -68,7 +68,6 @@
68
68
  "type": "PaywallBackground",
69
69
  "attributes": {
70
70
  "src": "https://images.unsplash.com/photo-1470770903676-69b98201ea1c?auto=format&fit=crop&w=1200&q=80",
71
- "resizeMode": "cover",
72
71
  "description": "Paywall background.",
73
72
  "title": "Paywall Background"
74
73
  },
@@ -68,7 +68,6 @@
68
68
  "type": "PaywallBackground",
69
69
  "attributes": {
70
70
  "src": "https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=1200&q=80",
71
- "resizeMode": "cover",
72
71
  "description": "Paywall background.",
73
72
  "title": "Paywall Background"
74
73
  },
@@ -104,7 +104,6 @@
104
104
  "type": "image",
105
105
  "attributes": {
106
106
  "src": "https://picsum.photos/720/320",
107
- "resizeMode": "cover",
108
107
  "description": "image element 1 in sample.",
109
108
  "title": "image 1",
110
109
  "styles": {
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from 'react';
2
+ import type { ImageStyle } from 'react-native';
2
3
  import type { NodeData } from '../../../types/Node';
3
4
  import type {
4
5
  ImagePropsGenerated,
@@ -7,11 +8,21 @@ import type {
7
8
  import { useBuilderParams } from '../../../components/BuilderProvider';
8
9
  import { extractImageStyleNative } from '../../../utils/extractImageStyle';
9
10
  import { defaultAppConfig } from '../../../types/PreviewConfig';
10
- import { getStyleBag } from '../../../utils/attributeStyle';
11
+ import {
12
+ getStyleBag,
13
+ toAttributeRecord,
14
+ stripStyleKeys,
15
+ type StyleAttrKey,
16
+ } from '../../../utils/attributeStyle';
11
17
 
12
18
  export function useExtractImageStyle<
13
19
  T extends ImagePropsGenerated['attributes'],
14
- >(node: NodeData<T>) {
20
+ >(
21
+ node: NodeData<T>,
22
+ ): {
23
+ style: ImageStyle;
24
+ other: Omit<T, StyleAttrKey> & { resizeMode?: ResizeModeOptionType };
25
+ } {
15
26
  const { appConfig, projectColors: builderProjectColors } = useBuilderParams();
16
27
  const theme = appConfig?.theme ?? defaultAppConfig.theme;
17
28
  const projectColors = builderProjectColors;
@@ -23,26 +34,19 @@ export function useExtractImageStyle<
23
34
  extractImageStyleNative(node, { theme, projectColors });
24
35
 
25
36
  const attrs = node.attributes;
26
-
27
- // Prefer the typed resizeMode from attributes, fall back to extracted style value.
37
+ const stripped = stripStyleKeys(toAttributeRecord(attrs));
28
38
  const imgStylesBag = getStyleBag(attrs);
39
+
40
+ // Prefer the typed resizeMode from style bag, fall back to extracted style value.
29
41
  const resizeMode = ((imgStylesBag?.resizeMode as string | undefined) ??
30
42
  resizeModeFromStyle) as ResizeModeOptionType | undefined;
31
43
 
32
- // Forward all non-style attributes; style bag is already consumed above.
33
- //Optimzation trade off by readability: fromEntries+filter avoids generic-to-Record double assertion.
34
- const forwardedAttrs = Object.fromEntries(
35
- Object.entries(attrs ?? {}).filter(
36
- ([key]) => key !== 'style' && key !== 'styles',
37
- ),
38
- );
39
-
40
44
  return {
41
- style,
45
+ style: style as ImageStyle,
42
46
  other: {
43
- ...forwardedAttrs,
47
+ ...stripped,
44
48
  resizeMode,
45
- },
49
+ } as Omit<T, StyleAttrKey> & { resizeMode?: ResizeModeOptionType },
46
50
  };
47
51
  }, [node, theme, projectColors]);
48
52
  }
@@ -1,14 +1,26 @@
1
1
  import { useMemo } from 'react';
2
+ import type { TextStyle } from 'react-native';
2
3
  import type { NodeData } from '../../../types/Node';
3
4
  import type { TextPropsGenerated } from '../../../build-components/Text/TextProps.generated';
4
5
  import { defaultAppConfig } from '../../../types/PreviewConfig';
5
6
  import { useBuilderParams } from '../../../components/BuilderProvider';
6
7
  import { extractTextStyleNative } from '../../../utils/extractTextStyle';
7
- import { getStyleBag } from '../../../utils/attributeStyle';
8
+ import {
9
+ getStyleBag,
10
+ toAttributeRecord,
11
+ stripStyleKeys,
12
+ type StyleAttrKey,
13
+ } from '../../../utils/attributeStyle';
8
14
 
9
15
  export function useExtractTextStyle<T extends TextPropsGenerated['attributes']>(
10
16
  node: NodeData<T>,
11
- ) {
17
+ ): {
18
+ style: TextStyle;
19
+ other: Omit<T, StyleAttrKey> & {
20
+ adjustsFontSizeToFit?: boolean;
21
+ showEllipsis?: boolean;
22
+ };
23
+ } {
12
24
  const {
13
25
  appConfig: builderAppConfig,
14
26
  projectColors: builderProjectColors,
@@ -27,25 +39,23 @@ export function useExtractTextStyle<T extends TextPropsGenerated['attributes']>(
27
39
  fonts,
28
40
  });
29
41
 
30
- const attrs = node.attributes ?? {};
42
+ const attrs = node.attributes;
43
+ const stripped = stripStyleKeys(toAttributeRecord(attrs));
31
44
  const styleBag = getStyleBag(attrs);
32
- const {
33
- style: _style,
34
- styles: _styles,
35
- ...rest
36
- } = attrs as Record<string, unknown>;
37
- void _style;
38
- void _styles;
39
45
 
40
46
  return {
41
- style,
47
+ style: style as TextStyle,
42
48
  other: {
43
- ...rest,
44
- // These are "behavior" flags stored under style in the schema.
45
- adjustsFontSizeToFit: styleBag?.adjustsFontSizeToFit as
49
+ ...stripped,
50
+ // These are "behavior" flags that may reside in the style bag.
51
+ adjustsFontSizeToFit: (stripped.adjustsFontSizeToFit ??
52
+ styleBag?.adjustsFontSizeToFit) as boolean | undefined,
53
+ showEllipsis: (stripped.showEllipsis ?? styleBag?.showEllipsis) as
46
54
  | boolean
47
55
  | undefined,
48
- showEllipsis: styleBag?.showEllipsis as boolean | undefined,
56
+ } as Omit<T, StyleAttrKey> & {
57
+ adjustsFontSizeToFit?: boolean;
58
+ showEllipsis?: boolean;
49
59
  },
50
60
  };
51
61
  },
@@ -1,44 +1,42 @@
1
1
  import { useMemo } from 'react';
2
+ import type { ViewStyle } from 'react-native';
2
3
  import type { NodeData } from '../../../types/Node';
3
4
  import type { ViewPropsGenerated } from '../../../build-components/View/ViewProps.generated';
4
5
  import { useBuilderParams } from '../../../components/BuilderProvider';
5
6
  import { extractViewStyleNative } from '../../../utils/extractViewStyle';
6
7
  import { defaultAppConfig } from '../../../types/PreviewConfig';
7
- import { getStyleBag, toAttributeRecord } from '../../../utils/attributeStyle';
8
+ import {
9
+ getStyleBag,
10
+ toAttributeRecord,
11
+ stripStyleKeys,
12
+ type StyleAttrKey,
13
+ } from '../../../utils/attributeStyle';
8
14
 
9
15
  export function useExtractViewStyle<T extends ViewPropsGenerated['attributes']>(
10
16
  node: NodeData<T>,
11
- ) {
17
+ ): {
18
+ style: ViewStyle;
19
+ other: Omit<T, StyleAttrKey> & { scrollable?: boolean };
20
+ } {
12
21
  const { appConfig, projectColors: builderProjectColors } = useBuilderParams();
13
22
  const theme = appConfig?.theme ?? defaultAppConfig.theme;
14
23
  const projectColors = builderProjectColors;
15
24
 
16
25
  return useMemo(() => {
17
26
  const style = extractViewStyleNative(node, { theme, projectColors });
18
-
19
27
  const attrs = node.attributes;
20
-
21
- // Forward all non-style attributes; style bag is already consumed by extractViewStyleNative.
22
- //Optimzation trade off by readability: fromEntries+filter avoids generic-to-Record double assertion.
23
- const forwardedAttrs = Object.fromEntries(
24
- Object.entries(attrs ?? {}).filter(
25
- ([key]) => key !== 'style' && key !== 'styles',
26
- ),
27
- );
28
-
29
- // scrollable may reside inside the styles bag at runtime (not on ViewStyleGenerated).
30
- const attrRecord = toAttributeRecord(attrs);
28
+ const stripped = stripStyleKeys(toAttributeRecord(attrs));
31
29
  const styleBag = getStyleBag(attrs);
32
- const scrollable = (attrRecord.scrollable ?? styleBag?.scrollable) as
33
- | boolean
34
- | undefined;
35
30
 
36
31
  return {
37
- style,
32
+ style: style as ViewStyle,
38
33
  other: {
39
- ...forwardedAttrs,
40
- scrollable,
41
- },
34
+ ...stripped,
35
+ // scrollable may reside inside the styles bag at runtime.
36
+ scrollable: (stripped.scrollable ?? styleBag?.scrollable) as
37
+ | boolean
38
+ | undefined,
39
+ } as Omit<T, StyleAttrKey> & { scrollable?: boolean },
42
40
  };
43
41
  }, [node, theme, projectColors]);
44
42
  }
@@ -63,6 +63,8 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
63
63
  firstAvailableTab,
64
64
  activeEntries,
65
65
  activeSpecialSections,
66
+ hasTitleField,
67
+ hasDescriptionField,
66
68
  hasStringChildren,
67
69
  childrenValue,
68
70
  handleChildrenChange,
@@ -94,37 +96,46 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
94
96
 
95
97
  const titleValue = getAttributeValue('title');
96
98
  const descriptionValue = getAttributeValue('description');
97
- const topFieldsSection = !isInvalidNode ? (
99
+ const showTopFields =
100
+ !isInvalidNode && (hasTitleField || hasDescriptionField);
101
+ const topFieldsSection = showTopFields ? (
98
102
  <section className="attributes-editor__top-fields">
99
- <div className="attributes-editor__field-wrapper">
100
- <label className="attributes-editor__field-label" htmlFor="title">
101
- Title (max 20 characters for display purposes)
102
- </label>
103
- <input
104
- id="title"
105
- className="input"
106
- type="text"
107
- maxLength={20}
108
- value={typeof titleValue === 'string' ? titleValue : ''}
109
- onChange={(event) =>
110
- handleAttributeChange('title', event.target.value)
111
- }
112
- />
113
- </div>
114
- <div className="attributes-editor__field-wrapper">
115
- <label className="attributes-editor__field-label" htmlFor="description">
116
- Description (for display purposes)
117
- </label>
118
- <input
119
- id="description"
120
- className="input"
121
- type="text"
122
- value={typeof descriptionValue === 'string' ? descriptionValue : ''}
123
- onChange={(event) =>
124
- handleAttributeChange('description', event.target.value)
125
- }
126
- />
127
- </div>
103
+ {hasTitleField ? (
104
+ <div className="attributes-editor__field-wrapper">
105
+ <label className="attributes-editor__field-label" htmlFor="title">
106
+ Title (max 20 characters for display purposes)
107
+ </label>
108
+ <input
109
+ id="title"
110
+ className="input"
111
+ type="text"
112
+ maxLength={20}
113
+ value={typeof titleValue === 'string' ? titleValue : ''}
114
+ onChange={(event) =>
115
+ handleAttributeChange('title', event.target.value)
116
+ }
117
+ />
118
+ </div>
119
+ ) : null}
120
+ {hasDescriptionField ? (
121
+ <div className="attributes-editor__field-wrapper">
122
+ <label
123
+ className="attributes-editor__field-label"
124
+ htmlFor="description"
125
+ >
126
+ Description (for display purposes)
127
+ </label>
128
+ <input
129
+ id="description"
130
+ className="input"
131
+ type="text"
132
+ value={typeof descriptionValue === 'string' ? descriptionValue : ''}
133
+ onChange={(event) =>
134
+ handleAttributeChange('description', event.target.value)
135
+ }
136
+ />
137
+ </div>
138
+ ) : null}
128
139
  </section>
129
140
  ) : null;
130
141
 
@@ -346,18 +357,14 @@ export function AttributesEditorView(props: AttributesEditorViewProps) {
346
357
  );
347
358
  }
348
359
 
349
- const sectionAttributes =
350
- section.meta?.category === 'style'
351
- ? ((styleBag ?? {}) as unknown as NodeDefaultAttribute)
352
- : attributes;
353
-
354
360
  return (
355
361
  <SpecialCategorySection
356
362
  key={section.key}
357
363
  category={section.key}
358
364
  entries={section.entries}
359
365
  attributeMeta={attributeMeta}
360
- attributes={sectionAttributes}
366
+ attributes={attributes}
367
+ getAttributeValue={getAttributeValue}
361
368
  onAttributeChange={handleAttributeChange}
362
369
  componentType={data?.type}
363
370
  projectColors={projectColorsForPicker}
@@ -16,6 +16,7 @@ type SpecialCategorySectionProps = {
16
16
  entries: SchemaEntry[];
17
17
  attributeMeta?: AttributeMetaMap;
18
18
  attributes: NodeDefaultAttribute;
19
+ getAttributeValue: (name: string) => unknown;
19
20
  onAttributeChange: (name: string, value: unknown) => void;
20
21
  componentType?: string;
21
22
  projectColors?: ProjectColors;
@@ -41,6 +42,7 @@ export function SpecialCategorySection({
41
42
  entries,
42
43
  attributeMeta,
43
44
  attributes,
45
+ getAttributeValue,
44
46
  onAttributeChange,
45
47
  componentType,
46
48
  projectColors,
@@ -96,7 +98,7 @@ export function SpecialCategorySection({
96
98
  const preferredScale = toPreferredScale(
97
99
  attributeMeta?.[name]?.preferedScale,
98
100
  );
99
- const currentValue = (attributes as Record<string, unknown>)[name];
101
+ const currentValue = getAttributeValue(name);
100
102
  const isBoolean = isBooleanFieldType(type);
101
103
  const fieldSlot = detectFieldSlot(name);
102
104
  const wrapperClassNames = [
@@ -175,7 +177,7 @@ export function SpecialCategorySection({
175
177
  const preferredScale = toPreferredScale(
176
178
  attributeMeta?.[name]?.preferedScale,
177
179
  );
178
- const currentValue = (attributes as Record<string, unknown>)[name];
180
+ const currentValue = getAttributeValue(name);
179
181
  const isBoolean = isBooleanFieldType(type);
180
182
  const fieldSlot = detectFieldSlot(name);
181
183
  const wrapperClassNames = [
@@ -220,7 +222,7 @@ export function SpecialCategorySection({
220
222
  const preferredScale = toPreferredScale(
221
223
  attributeMeta?.[name]?.preferedScale,
222
224
  );
223
- const currentValue = (attributes as Record<string, unknown>)[name];
225
+ const currentValue = getAttributeValue(name);
224
226
  const isBoolean = isBooleanFieldType(type);
225
227
  const wrapperClassNames = [
226
228
  'attributes-editor__field-wrapper',
@@ -81,6 +81,8 @@ export type AttributesEditorModel = {
81
81
  mockableFeatureKeys: string[];
82
82
  activeMockableFeature: string | null;
83
83
  setActiveMockableFeature: (next: string | null) => void;
84
+ hasTitleField: boolean;
85
+ hasDescriptionField: boolean;
84
86
  hasStringChildren: boolean;
85
87
  childrenValue: string;
86
88
  };