@developer_tribe/react-builder 1.0.8 → 1.0.9

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 (216) hide show
  1. package/dist/build-components/BIcon/BIconProps.generated.d.ts +3 -0
  2. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +1 -0
  3. package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -0
  4. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +5 -0
  5. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +1 -0
  6. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +1 -0
  7. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -0
  8. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -0
  9. package/dist/build-components/Image/ImageProps.generated.d.ts +1 -0
  10. package/dist/build-components/Main/MainProps.generated.d.ts +1 -1
  11. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -0
  12. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +1 -0
  13. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +1 -0
  14. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +1 -0
  15. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +3 -0
  16. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -0
  17. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +1 -0
  18. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +3 -0
  19. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +3 -0
  20. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +3 -0
  21. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +1 -1
  22. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +3 -1
  23. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
  24. package/dist/build-components/PaywallProvider/PaywallContext.d.ts +12 -0
  25. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -1
  26. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +1 -0
  27. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +1 -1
  28. package/dist/build-components/Text/TextProps.generated.d.ts +3 -0
  29. package/dist/build-components/View/ViewProps.generated.d.ts +1 -0
  30. package/dist/build-components/patterns.generated.d.ts +372 -374
  31. package/dist/components/BuilderProvider.d.ts +2 -0
  32. package/dist/components/ParamsProvider.d.ts +5 -0
  33. package/dist/components/RenderErrorBoundary.d.ts +28 -0
  34. package/dist/hooks/useSyncHtmlThemeClass.d.ts +7 -0
  35. package/dist/index.cjs.js +5 -5
  36. package/dist/index.cjs.js.map +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.esm.js +3 -3
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/index.native.cjs.js +4 -4
  41. package/dist/index.native.cjs.js.map +1 -1
  42. package/dist/index.native.d.ts +1 -0
  43. package/dist/index.native.esm.js +4 -4
  44. package/dist/index.native.esm.js.map +1 -1
  45. package/dist/migrations/migratePipe.d.ts +14 -0
  46. package/dist/migrations/migrations/1.1.0_normalize_style_attributes.d.ts +2 -0
  47. package/dist/migrations/semver.d.ts +8 -0
  48. package/dist/migrations/types.d.ts +8 -0
  49. package/dist/mockOS/components/SubscriptionModal.d.ts +7 -0
  50. package/dist/mockOS/context/MockOSContextBase.d.ts +1 -0
  51. package/dist/mockOS/hooks/useMockIap.d.ts +3 -0
  52. package/dist/mockOS/index.d.ts +4 -0
  53. package/dist/mockOS/managers/mockOSIapManager.d.ts +6 -0
  54. package/dist/mockOS/managers/subscriptionManager.d.ts +10 -0
  55. package/dist/pages/ProjectDebug.d.ts +14 -0
  56. package/dist/pages/ProjectMigrationPage.d.ts +23 -0
  57. package/dist/pages/ProjectValidationPage.d.ts +15 -0
  58. package/dist/styles.css +1 -1
  59. package/dist/types/Device.d.ts +5 -0
  60. package/dist/utils/__special_exceptions.d.ts +7 -0
  61. package/dist/utils/getImage.d.ts +23 -0
  62. package/dist/utils/pasteNode.d.ts +15 -0
  63. package/dist/utils/patterns.d.ts +1 -2
  64. package/package.json +6 -2
  65. package/scripts/migrate-patterns-to-v2.mjs +131 -0
  66. package/scripts/migrate-samples-to-current.ts +79 -0
  67. package/scripts/prebuild/utils/createGeneratedProps.js +4 -5
  68. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +32 -21
  69. package/scripts/prebuild/utils/validatePatternJson.js +12 -10
  70. package/src/.DS_Store +0 -0
  71. package/src/AttributesEditor.tsx +41 -11
  72. package/src/RenderPage.tsx +55 -0
  73. package/src/assets/.DS_Store +0 -0
  74. package/src/assets/devices.json +91 -0
  75. package/src/assets/samples/carousel-sample.json +141 -29
  76. package/src/assets/samples/getSamples.ts +9 -0
  77. package/src/assets/samples/paywall-1.json +119 -71
  78. package/src/assets/samples/simple-1.json +28 -16
  79. package/src/assets/samples/simple-2.json +157 -82
  80. package/src/assets/samples/unmigrated-builder1.json +42 -0
  81. package/src/assets/samples/unvalidated-builder1.json +49 -0
  82. package/src/assets/samples/unvalidated-crash1.json +19 -0
  83. package/src/assets/samples/unvalidated-crashcomponent1.json +16 -0
  84. package/src/assets/samples/vpn-onboard-1.json +91 -51
  85. package/src/assets/samples/vpn-onboard-2.json +318 -278
  86. package/src/assets/samples/vpn-onboard-3.json +286 -252
  87. package/src/assets/samples/vpn-onboard-4.json +286 -252
  88. package/src/assets/samples/vpn-onboard-5.json +434 -374
  89. package/src/assets/samples/vpn-onboard-6.json +290 -250
  90. package/src/attributes-editor/Field.tsx +1 -1
  91. package/src/attributes-editor/LayoutPreviewPicker.tsx +5 -2
  92. package/src/build-components/BIcon/BIconProps.generated.ts +3 -0
  93. package/src/build-components/BIcon/pattern.json +12 -9
  94. package/src/build-components/BackgroundImage/BackgroundImage.tsx +3 -1
  95. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +1 -0
  96. package/src/build-components/BackgroundImage/pattern.json +25 -16
  97. package/src/build-components/Button/Button.tsx +26 -3
  98. package/src/build-components/Button/ButtonProps.generated.ts +1 -0
  99. package/src/build-components/Button/pattern.json +10 -6
  100. package/src/build-components/Carousel/CarouselProps.generated.ts +5 -0
  101. package/src/build-components/Carousel/pattern.json +19 -8
  102. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +1 -0
  103. package/src/build-components/CarouselButtons/pattern.json +11 -5
  104. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +1 -0
  105. package/src/build-components/CarouselDots/pattern.json +5 -4
  106. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -0
  107. package/src/build-components/CarouselItem/pattern.json +5 -4
  108. package/src/build-components/CarouselProvider/CarouselProvider.tsx +44 -2
  109. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -0
  110. package/src/build-components/Image/Image.tsx +2 -1
  111. package/src/build-components/Image/ImageProps.generated.ts +1 -0
  112. package/src/build-components/Image/pattern.json +11 -5
  113. package/src/build-components/Main/MainProps.generated.ts +1 -1
  114. package/src/build-components/Main/pattern.json +12 -9
  115. package/src/build-components/Onboard/OnboardProps.generated.ts +1 -0
  116. package/src/build-components/Onboard/pattern.json +14 -9
  117. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +1 -0
  118. package/src/build-components/OnboardButton/pattern.json +5 -4
  119. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +1 -0
  120. package/src/build-components/OnboardButtons/pattern.json +5 -4
  121. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +1 -0
  122. package/src/build-components/OnboardDot/pattern.json +5 -4
  123. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +3 -0
  124. package/src/build-components/OnboardFooter/pattern.json +8 -5
  125. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -0
  126. package/src/build-components/OnboardImage/pattern.json +7 -4
  127. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +1 -0
  128. package/src/build-components/OnboardItem/pattern.json +18 -9
  129. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +3 -0
  130. package/src/build-components/OnboardProvider/pattern.json +21 -6
  131. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +3 -0
  132. package/src/build-components/OnboardSubtitle/pattern.json +10 -6
  133. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +3 -0
  134. package/src/build-components/OnboardTitle/pattern.json +11 -7
  135. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +1 -1
  136. package/src/build-components/PaywallBackground/pattern.json +5 -4
  137. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +6 -1
  138. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +3 -1
  139. package/src/build-components/PaywallCloseButton/pattern.json +15 -12
  140. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +0 -1
  141. package/src/build-components/PaywallOptions/PaywallOptions.tsx +3 -2
  142. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
  143. package/src/build-components/PaywallOptions/pattern.json +14 -11
  144. package/src/build-components/PaywallProvider/PaywallContext.ts +25 -0
  145. package/src/build-components/PaywallProvider/PaywallProvider.tsx +102 -5
  146. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -1
  147. package/src/build-components/PaywallProvider/pattern.json +11 -8
  148. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +7 -0
  149. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +1 -0
  150. package/src/build-components/PaywallSubscribeButton/pattern.json +16 -13
  151. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +1 -1
  152. package/src/build-components/RadioButton/pattern.json +5 -4
  153. package/src/build-components/Text/Text.tsx +107 -4
  154. package/src/build-components/Text/TextProps.generated.ts +3 -0
  155. package/src/build-components/Text/pattern.json +19 -4
  156. package/src/build-components/View/ViewProps.generated.ts +1 -0
  157. package/src/build-components/View/pattern.json +28 -13
  158. package/src/build-components/other.tsx +15 -0
  159. package/src/build-components/patterns.generated.ts +340 -235
  160. package/src/build-components/useNode.ts +22 -3
  161. package/src/components/Builder.tsx +20 -6
  162. package/src/components/BuilderButton.tsx +75 -38
  163. package/src/components/BuilderProvider.tsx +22 -2
  164. package/src/components/DeviceButton.tsx +12 -5
  165. package/src/components/EditorHeader.tsx +296 -38
  166. package/src/components/ParamsProvider.tsx +7 -0
  167. package/src/components/RenderErrorBoundary.tsx +200 -0
  168. package/src/hooks/useParams.ts +5 -1
  169. package/src/hooks/useSyncHtmlThemeClass.ts +19 -0
  170. package/src/index.native.ts +7 -0
  171. package/src/index.ts +8 -0
  172. package/src/migrations/migratePipe.ts +59 -0
  173. package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +80 -0
  174. package/src/migrations/semver.ts +24 -0
  175. package/src/migrations/types.ts +9 -0
  176. package/src/mockOS/components/PermissionModal.tsx +3 -2
  177. package/src/mockOS/components/SubscriptionModal.tsx +400 -0
  178. package/src/mockOS/context/MockOSContext.tsx +61 -10
  179. package/src/mockOS/context/MockOSContextBase.ts +1 -0
  180. package/src/mockOS/hooks/useMockIap.ts +11 -0
  181. package/src/mockOS/index.ts +7 -0
  182. package/src/mockOS/managers/mockOSIapManager.ts +10 -0
  183. package/src/mockOS/managers/subscriptionManager.ts +36 -0
  184. package/src/modals/IconPickerModal.tsx +1 -1
  185. package/src/pages/ProjectDebug.tsx +331 -0
  186. package/src/pages/ProjectMigrationPage.tsx +92 -0
  187. package/src/pages/ProjectPage.tsx +313 -161
  188. package/src/pages/ProjectValidationPage.tsx +54 -0
  189. package/src/styles/base/_global.scss +58 -11
  190. package/src/styles/components/_attributes-editor.scss +1 -1
  191. package/src/styles/components/_bottom-bar.scss +7 -4
  192. package/src/styles/components/_editor-shell.scss +126 -4
  193. package/src/styles/components/_mockos-router.scss +3 -2
  194. package/src/styles/components/_ui-components.scss +10 -5
  195. package/src/styles/foundation/_colors.scss +78 -11
  196. package/src/styles/foundation/_mixins.scss +4 -1
  197. package/src/styles/foundation/_sizes.scss +4 -2
  198. package/src/styles/index.scss +1 -0
  199. package/src/styles/layout/_builder.scss +61 -0
  200. package/src/styles/layout/_project-validation.scss +214 -0
  201. package/src/styles/modals/_add-component.scss +4 -2
  202. package/src/styles/modals/_color-modal.scss +4 -2
  203. package/src/styles/modals/_modal-shell.scss +3 -1
  204. package/src/types/Device.ts +5 -0
  205. package/src/utils/__special_exceptions.ts +88 -0
  206. package/src/utils/analyseNode.ts +8 -2
  207. package/src/utils/analyseNodeByPatterns.ts +43 -9
  208. package/src/utils/extractTextStyle.ts +19 -6
  209. package/src/utils/extractViewStyle.ts +68 -59
  210. package/src/utils/getImage.ts +76 -0
  211. package/src/utils/novaToJson.ts +2 -1
  212. package/src/utils/pasteNode.ts +172 -0
  213. package/src/utils/patterns.ts +4 -3
  214. package/dist/android.svg +0 -43
  215. package/dist/apple.svg +0 -16
  216. package/dist/background.jpg +0 -0
@@ -1,4 +1,4 @@
1
- import React, { useId, useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useId, useMemo, useState } from 'react';
2
2
  import type { PaywallProviderComponentProps } from './PaywallProviderProps.generated';
3
3
  import RenderNode from '../RenderNode.generated';
4
4
  import type { Node } from '../../types/Node';
@@ -10,6 +10,8 @@ import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
10
10
  import { useMergedStyle } from '../../utils/useMergedStyle';
11
11
  import { LocalizationParamsProvider } from '../../components/LocalizationParamsProvider';
12
12
  import { useBuilderParams } from '../../components/BuilderProvider';
13
+ import { PaywallContext } from './PaywallContext';
14
+ import { useMockOSContext } from '../../mockOS/context/MockOSContextBase';
13
15
 
14
16
  function PaywallProvider({ node }: PaywallProviderComponentProps) {
15
17
  useLogRender('PaywallProvider');
@@ -29,7 +31,9 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
29
31
  }),
30
32
  );
31
33
 
32
- const { benefits } = useBuilderParams();
34
+ const { benefits, products, onPaywallClose, onPaywallSubscribe } =
35
+ useBuilderParams();
36
+ const mockOS = useMockOSContext();
33
37
  const benefitLocalizationParams = useMemo(() => {
34
38
  const raw =
35
39
  benefits && typeof benefits === 'object' && !Array.isArray(benefits)
@@ -62,15 +66,108 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
62
66
  isSelected ? SELECTED_OUTLINE_STYLE : undefined,
63
67
  );
64
68
 
69
+ const [selectedProductId, setSelectedProductId] = useState<string>('');
70
+ useEffect(() => {
71
+ const list = Array.isArray(products) ? products : [];
72
+ if (list.length === 0) {
73
+ if (selectedProductId) setSelectedProductId('');
74
+ return;
75
+ }
76
+
77
+ const exists = list.some((p) => p?.productId === selectedProductId);
78
+ if (!selectedProductId || !exists) {
79
+ setSelectedProductId(list[0]?.productId ?? '');
80
+ }
81
+ }, [products, selectedProductId]);
82
+
83
+ const selectedProduct = useMemo(() => {
84
+ const list = Array.isArray(products) ? products : [];
85
+ return (
86
+ list.find((p) => p?.productId === selectedProductId) ??
87
+ (list.length > 0 ? list[0] : undefined)
88
+ );
89
+ }, [products, selectedProductId]);
90
+
91
+ const handleClose = useCallback(() => {
92
+ // Host app override wins.
93
+ if (onPaywallClose) {
94
+ onPaywallClose();
95
+ }
96
+
97
+ // Default: in MockOS go back (simulate dismissing paywall screen).
98
+ // TODO: at react native merge it will change
99
+ if (mockOS?.isEnabled && mockOS.goBack) {
100
+ mockOS.goBack();
101
+ }
102
+ }, [mockOS, onPaywallClose]);
103
+
104
+ const handleSubscribe = useCallback(
105
+ async (product?: { productId?: string }) => {
106
+ // Host app override wins.
107
+ if (onPaywallSubscribe) {
108
+ const result = onPaywallSubscribe(product as any);
109
+ const ok =
110
+ result instanceof Promise
111
+ ? await result
112
+ : result === true
113
+ ? true
114
+ : false;
115
+
116
+ // If host signals success, go back (OS return).
117
+ if (ok && mockOS?.isEnabled) {
118
+ mockOS.goBack?.();
119
+ }
120
+ return;
121
+ }
122
+
123
+ // Default: in MockOS show a native-like subscription prompt.
124
+ // TODO: at react native merge it will change
125
+ const productId = product?.productId;
126
+ if (
127
+ mockOS?.isEnabled &&
128
+ productId &&
129
+ mockOS.requestSubscriptionPurchase
130
+ ) {
131
+ const ok = await mockOS.requestSubscriptionPurchase(productId);
132
+ if (ok) {
133
+ // On success, go back (simulate dismissing paywall after purchase).
134
+ // TODO: at react native merge it will change
135
+ mockOS.goBack?.();
136
+ }
137
+ }
138
+ },
139
+ [mockOS, onPaywallSubscribe],
140
+ );
141
+
142
+ const paywallContextValue = useMemo(
143
+ () => ({
144
+ products: Array.isArray(products) ? products : [],
145
+ selectedProductId,
146
+ setSelectedProductId,
147
+ selectedProduct,
148
+ onClose: handleClose,
149
+ onSubscribe: handleSubscribe as any,
150
+ }),
151
+ [
152
+ products,
153
+ selectedProductId,
154
+ selectedProduct,
155
+ handleClose,
156
+ handleSubscribe,
157
+ ],
158
+ );
159
+
65
160
  return (
66
161
  <div
67
162
  attribute-name={attributeName}
68
163
  attribute-key={attributeKey}
69
164
  style={style}
70
165
  >
71
- <LocalizationParamsProvider params={benefitLocalizationParams}>
72
- <RenderNode node={node.children as Node} />
73
- </LocalizationParamsProvider>
166
+ <PaywallContext.Provider value={paywallContextValue}>
167
+ <LocalizationParamsProvider params={benefitLocalizationParams}>
168
+ <RenderNode node={node.children as Node} />
169
+ </LocalizationParamsProvider>
170
+ </PaywallContext.Provider>
74
171
  </div>
75
172
  );
76
173
  }
@@ -21,6 +21,7 @@ export type PositionOptionType = 'relative' | 'absolute';
21
21
  export interface PaywallProviderPropsGenerated {
22
22
  child: string;
23
23
  attributes: {
24
+ style?: Record<string, unknown>;
24
25
  scrollable?: boolean;
25
26
  flexDirection?: FlexDirectionOptionType;
26
27
  alignItems?: AlignItemsOptionType;
@@ -54,7 +55,6 @@ export interface PaywallProviderPropsGenerated {
54
55
  left?: string;
55
56
  right?: string;
56
57
  zIndex?: number;
57
- [key: string]: string | number | boolean | undefined;
58
58
  };
59
59
  }
60
60
 
@@ -1,24 +1,27 @@
1
1
  {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": true,
2
+ "schemaVersion": 2,
4
3
  "pattern": {
5
4
  "type": "PaywallProvider",
6
- "children": ["node", "array"],
5
+ "children": "node",
7
6
  "extends": "View",
8
7
  "attributes": {}
9
8
  },
10
9
  "defaults": {
11
- "width": "100%",
12
- "height": "100%"
10
+ "style": {
11
+ "width": "100%",
12
+ "height": "100%"
13
+ }
13
14
  },
14
15
  "meta": {
15
- "desiredParent": ["all"],
16
+ "desiredParent": [
17
+ "all"
18
+ ],
16
19
  "label": "Paywall Provider",
17
20
  "description": "Provider/wrapper for paywall screen components.",
18
- "attributes": {},
19
21
  "mockableFeatures": {
20
22
  "products": true,
21
23
  "benefits": true
22
- }
24
+ },
25
+ "styles": {}
23
26
  }
24
27
  }
@@ -7,12 +7,14 @@ import { extractTextStyle } from '../../utils/extractTextStyle';
7
7
  import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
8
8
  import { useMergedStyle } from '../../utils/useMergedStyle';
9
9
  import { useLocalize } from '../../hooks/useLocalize';
10
+ import { usePaywallContext } from '../PaywallProvider/PaywallContext';
10
11
 
11
12
  function PaywallSubscribeButton({
12
13
  node,
13
14
  }: PaywallSubscribeButtonComponentProps) {
14
15
  useLogRender('PaywallSubscribeButton');
15
16
  node = useNode(node);
17
+ const { onSubscribe, selectedProduct } = usePaywallContext();
16
18
 
17
19
  const generatedId = useId();
18
20
  const attributeName =
@@ -50,6 +52,11 @@ function PaywallSubscribeButton({
50
52
  type="button"
51
53
  attribute-name={attributeName}
52
54
  attribute-key={attributeKey}
55
+ href="#"
56
+ onClick={(e) => {
57
+ e.preventDefault();
58
+ onSubscribe?.(selectedProduct);
59
+ }}
53
60
  style={{ ...style, cursor: 'pointer' }}
54
61
  >
55
62
  {localize(label)}
@@ -33,6 +33,7 @@ export type PositionOptionType = 'relative' | 'absolute';
33
33
  export interface PaywallSubscribeButtonPropsGenerated {
34
34
  child: string;
35
35
  attributes: {
36
+ style?: Record<string, unknown>;
36
37
  color?: string;
37
38
  fontSize?: string;
38
39
  fontWeight?: FontWeightOptionType;
@@ -1,6 +1,5 @@
1
1
  {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": false,
2
+ "schemaVersion": 2,
4
3
  "pattern": {
5
4
  "type": "PaywallSubscribeButton",
6
5
  "children": "string",
@@ -8,20 +7,24 @@
8
7
  "attributes": {}
9
8
  },
10
9
  "defaults": {
11
- "paddingHorizontal": "20@s",
12
- "paddingVertical": "12@vs",
13
- "borderRadius": "12@s",
14
- "backgroundColor": "#6495ED",
15
- "color": "#FFFFFF",
16
- "fontSize": "16@fs",
17
- "fontWeight": "700",
18
- "justifyContent": "center",
19
- "alignItems": "center"
10
+ "style": {
11
+ "paddingHorizontal": "20@s",
12
+ "paddingVertical": "12@vs",
13
+ "borderRadius": "12@s",
14
+ "backgroundColor": "#6495ED",
15
+ "color": "#FFFFFF",
16
+ "fontSize": "16@fs",
17
+ "fontWeight": "700",
18
+ "justifyContent": "center",
19
+ "alignItems": "center"
20
+ }
20
21
  },
21
22
  "meta": {
22
- "desiredParent": [">PaywallProvider"],
23
+ "desiredParent": [
24
+ ">PaywallProvider"
25
+ ],
23
26
  "label": "Paywall Subscribe Button",
24
27
  "description": "Paywall subscribe call-to-action button. Extends Button.",
25
- "attributes": {}
28
+ "styles": {}
26
29
  }
27
30
  }
@@ -21,6 +21,7 @@ export type PositionOptionType = 'relative' | 'absolute';
21
21
  export interface RadioButtonPropsGenerated {
22
22
  child: string;
23
23
  attributes: {
24
+ style?: Record<string, unknown>;
24
25
  scrollable?: boolean;
25
26
  flexDirection?: FlexDirectionOptionType;
26
27
  alignItems?: AlignItemsOptionType;
@@ -57,7 +58,6 @@ export interface RadioButtonPropsGenerated {
57
58
  selected?: boolean;
58
59
  color?: string;
59
60
  size?: number;
60
- [key: string]: string | number | boolean | undefined;
61
61
  };
62
62
  }
63
63
 
@@ -1,6 +1,5 @@
1
1
  {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": true,
2
+ "schemaVersion": 2,
4
3
  "pattern": {
5
4
  "type": "RadioButton",
6
5
  "children": "never",
@@ -12,10 +11,12 @@
12
11
  }
13
12
  },
14
13
  "meta": {
15
- "desiredParent": ["all"],
14
+ "desiredParent": [
15
+ "all"
16
+ ],
16
17
  "label": "Radio Button",
17
18
  "description": "Generic radio button icon.",
18
- "attributes": {
19
+ "styles": {
19
20
  "selected": {
20
21
  "label": "Selected",
21
22
  "description": "Whether the radio is selected.",
@@ -1,4 +1,10 @@
1
- import React, { useId, useMemo } from 'react';
1
+ import React, {
2
+ useId,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import type { TextComponentProps } from './TextProps.generated';
3
9
  import useNode from '../useNode';
4
10
  import { useRenderStore } from '../../store';
@@ -14,6 +20,8 @@ function Text({ node }: TextComponentProps) {
14
20
  const generatedId = useId();
15
21
  const attributeName = (node as any)?.sourceType ?? node.type ?? 'text';
16
22
  const attributeKey = node.key ?? generatedId;
23
+ const textRef = useRef<HTMLParagraphElement | null>(null);
24
+ const [autoFontSizePx, setAutoFontSizePx] = useState<number | null>(null);
17
25
  const { appConfig, projectColors, previewMode, current } = useRenderStore(
18
26
  (s) => ({
19
27
  appConfig: s.appConfig,
@@ -28,18 +36,113 @@ function Text({ node }: TextComponentProps) {
28
36
  [node, appConfig, projectColors],
29
37
  );
30
38
  const isSelected = isNodeSelected({ previewMode, current, node });
39
+ const localize = useLocalize();
40
+
41
+ const localizedText = useMemo(
42
+ () => localize(keyOrText),
43
+ [localize, keyOrText],
44
+ );
45
+
46
+ const attrs = (node as any)?.attributes as
47
+ | (Record<string, unknown> & { style?: Record<string, unknown> })
48
+ | undefined;
49
+ const styleBag = attrs?.style;
50
+ const adjustsFontSizeToFit =
51
+ (attrs?.adjustsFontSizeToFit as boolean | undefined) ??
52
+ (styleBag?.adjustsFontSizeToFit as boolean | undefined) ??
53
+ false;
54
+ const showEllipsis =
55
+ (attrs?.showEllipsis as boolean | undefined) ??
56
+ (styleBag?.showEllipsis as boolean | undefined) ??
57
+ false;
58
+
59
+ useLayoutEffect(() => {
60
+ if (!adjustsFontSizeToFit) {
61
+ setAutoFontSizePx(null);
62
+ return;
63
+ }
64
+
65
+ const el = textRef.current;
66
+ if (!el) return;
67
+
68
+ const compute = () => {
69
+ const availableWidth = el.clientWidth;
70
+ const availableHeight = el.clientHeight;
71
+ const len = localizedText?.length ?? 0;
72
+
73
+ if (!availableWidth || !availableHeight || !len) {
74
+ setAutoFontSizePx(null);
75
+ return;
76
+ }
77
+
78
+ // TODO: fontSize = Math.sqrt(textWidth*textHeight/text.length)
79
+ const candidate = Math.sqrt((availableWidth * availableHeight) / len);
80
+ const computedFontSizePx = Number.parseFloat(
81
+ window.getComputedStyle(el).fontSize,
82
+ );
83
+ const maxPx = Number.isFinite(computedFontSizePx)
84
+ ? computedFontSizePx
85
+ : undefined;
86
+
87
+ // Keep it within a sane range and never increase beyond current font size.
88
+ const clamped = Math.max(
89
+ 8,
90
+ maxPx ? Math.min(candidate, maxPx) : candidate,
91
+ );
92
+ setAutoFontSizePx(Number.isFinite(clamped) ? clamped : null);
93
+ };
94
+
95
+ compute();
96
+
97
+ let ro: ResizeObserver | null = null;
98
+ if (typeof ResizeObserver !== 'undefined') {
99
+ ro = new ResizeObserver(() => compute());
100
+ ro.observe(el);
101
+ } else {
102
+ window.addEventListener('resize', compute);
103
+ }
104
+
105
+ return () => {
106
+ ro?.disconnect();
107
+ window.removeEventListener('resize', compute);
108
+ };
109
+ }, [adjustsFontSizeToFit, localizedText]);
110
+
111
+ const extraStyle = useMemo((): React.CSSProperties | undefined => {
112
+ let next: React.CSSProperties | undefined;
113
+
114
+ if (showEllipsis) {
115
+ next = {
116
+ ...(next ?? {}),
117
+ overflow: 'hidden',
118
+ textOverflow: 'ellipsis',
119
+ whiteSpace: 'nowrap',
120
+ };
121
+ }
122
+
123
+ if (autoFontSizePx !== null) {
124
+ next = {
125
+ ...(next ?? {}),
126
+ fontSize: `${autoFontSizePx}px`,
127
+ };
128
+ }
129
+
130
+ return next;
131
+ }, [showEllipsis, autoFontSizePx]);
132
+
31
133
  const style = useMergedStyle(
32
- textStyle,
134
+ useMergedStyle(textStyle, extraStyle),
33
135
  isSelected ? SELECTED_OUTLINE_STYLE : undefined,
34
136
  );
35
- const localize = useLocalize();
137
+
36
138
  return (
37
139
  <p
140
+ ref={textRef}
38
141
  attribute-name={attributeName}
39
142
  attribute-key={attributeKey}
40
143
  style={style}
41
144
  >
42
- {localize(keyOrText)}
145
+ {localizedText}
43
146
  </p>
44
147
  );
45
148
  }
@@ -34,6 +34,7 @@ export type TextAlignOptionType = 'left' | 'center' | 'right' | 'justify';
34
34
  export interface TextPropsGenerated {
35
35
  child: string;
36
36
  attributes: {
37
+ style?: Record<string, unknown>;
37
38
  scrollable?: boolean;
38
39
  flexDirection?: FlexDirectionOptionType;
39
40
  alignItems?: AlignItemsOptionType;
@@ -71,6 +72,8 @@ export interface TextPropsGenerated {
71
72
  fontSize?: string;
72
73
  fontWeight?: FontWeightOptionType;
73
74
  textAlign?: TextAlignOptionType;
75
+ adjustsFontSizeToFit?: boolean;
76
+ showEllipsis?: boolean;
74
77
  };
75
78
  }
76
79
 
@@ -1,6 +1,5 @@
1
1
  {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": false,
2
+ "schemaVersion": 2,
4
3
  "pattern": {
5
4
  "type": "Text",
6
5
  "children": "string",
@@ -21,14 +20,16 @@
21
20
  "800",
22
21
  "900"
23
22
  ],
24
- "textAlign": ["left", "center", "right", "justify"]
23
+ "textAlign": ["left", "center", "right", "justify"],
24
+ "adjustsFontSizeToFit": "boolean",
25
+ "showEllipsis": "boolean"
25
26
  }
26
27
  },
27
28
  "meta": {
28
29
  "desiredParent": ["all"],
29
30
  "label": "Text",
30
31
  "description": "Displays simple text.",
31
- "attributes": {
32
+ "styles": {
32
33
  "color": {
33
34
  "label": "Color",
34
35
  "description": "Text color.",
@@ -57,6 +58,20 @@
57
58
  "category": "style",
58
59
  "specialCategory": null,
59
60
  "sort": 4
61
+ },
62
+ "adjustsFontSizeToFit": {
63
+ "label": "Adjust Font Size To Fit",
64
+ "description": "Automatically reduces font size to fit the available space.",
65
+ "category": "style",
66
+ "specialCategory": null,
67
+ "sort": 5
68
+ },
69
+ "showEllipsis": {
70
+ "label": "Show Ellipsis",
71
+ "description": "If text overflows, show ellipsis (…); applied as single-line truncation.",
72
+ "category": "style",
73
+ "specialCategory": null,
74
+ "sort": 6
60
75
  }
61
76
  }
62
77
  }
@@ -21,6 +21,7 @@ export type PositionOptionType = 'relative' | 'absolute';
21
21
  export interface ViewPropsGenerated {
22
22
  child: string;
23
23
  attributes: {
24
+ style?: Record<string, unknown>;
24
25
  scrollable?: boolean;
25
26
  flexDirection?: FlexDirectionOptionType;
26
27
  alignItems?: AlignItemsOptionType;
@@ -1,13 +1,21 @@
1
1
  {
2
- "schemaVersion": 1,
3
- "allowUnknownAttributes": false,
2
+ "schemaVersion": 2,
4
3
  "pattern": {
5
4
  "type": "View",
6
- "children": ["node", "array"],
5
+ "children": "node",
7
6
  "attributes": {
8
7
  "scrollable": "boolean",
9
- "flexDirection": ["row", "column"],
10
- "alignItems": ["flex-start", "center", "flex-end", "stretch", "baseline"],
8
+ "flexDirection": [
9
+ "row",
10
+ "column"
11
+ ],
12
+ "alignItems": [
13
+ "flex-start",
14
+ "center",
15
+ "flex-end",
16
+ "stretch",
17
+ "baseline"
18
+ ],
11
19
  "justifyContent": [
12
20
  "flex-start",
13
21
  "center",
@@ -39,21 +47,21 @@
39
47
  "minHeight": "size",
40
48
  "maxHeight": "size",
41
49
  "flex": "number",
42
- "position": ["relative", "absolute"],
50
+ "position": [
51
+ "relative",
52
+ "absolute"
53
+ ],
43
54
  "top": "size",
44
55
  "bottom": "size",
45
56
  "left": "size",
46
57
  "right": "size",
47
58
  "zIndex": "number"
48
- },
49
- "defaults": {
50
- "flexDirection": "column",
51
- "position": "relative",
52
- "zIndex": 1
53
59
  }
54
60
  },
55
61
  "meta": {
56
- "desiredParent": ["all"],
62
+ "desiredParent": [
63
+ "all"
64
+ ],
57
65
  "label": "View",
58
66
  "description": "Base layout container.",
59
67
  "specialCategories": {
@@ -82,7 +90,7 @@
82
90
  "sort": 4
83
91
  }
84
92
  },
85
- "attributes": {
93
+ "styles": {
86
94
  "scrollable": {
87
95
  "label": "Scrollable",
88
96
  "description": "Turns scroll interaction on.",
@@ -348,5 +356,12 @@
348
356
  "sort": 26
349
357
  }
350
358
  }
359
+ },
360
+ "defaults": {
361
+ "style": {
362
+ "flexDirection": "column",
363
+ "position": "relative",
364
+ "zIndex": 1
365
+ }
351
366
  }
352
367
  }
@@ -2,5 +2,20 @@ import React from 'react';
2
2
  import type { Node } from '../types/Node';
3
3
 
4
4
  export function other(type: string, node: Node): React.ReactNode {
5
+ // Intentionally-crashing component for testing error boundaries / unvalidated flows.
6
+ // NOTE: This is NOT part of the generated component patterns, so it will never
7
+ // show up in the "Add component" modal. It can only be used by loading JSON
8
+ // that contains { type: "CrashComponent" }.
9
+ if (type === 'CrashComponent') {
10
+ const details =
11
+ node && typeof node === 'object' && !Array.isArray(node)
12
+ ? (node as any).attributes
13
+ : undefined;
14
+ throw new Error(
15
+ `CrashComponent: intentional crash for testing${
16
+ details ? ` (attributes=${JSON.stringify(details)})` : ''
17
+ }`,
18
+ );
19
+ }
5
20
  return null;
6
21
  }