@developer_tribe/react-builder 1.0.7 → 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 (217) 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/BottomBar.tsx +45 -45
  162. package/src/components/Builder.tsx +20 -6
  163. package/src/components/BuilderButton.tsx +75 -38
  164. package/src/components/BuilderProvider.tsx +22 -2
  165. package/src/components/DeviceButton.tsx +12 -5
  166. package/src/components/EditorHeader.tsx +296 -38
  167. package/src/components/ParamsProvider.tsx +7 -0
  168. package/src/components/RenderErrorBoundary.tsx +200 -0
  169. package/src/hooks/useParams.ts +5 -1
  170. package/src/hooks/useSyncHtmlThemeClass.ts +19 -0
  171. package/src/index.native.ts +7 -0
  172. package/src/index.ts +8 -0
  173. package/src/migrations/migratePipe.ts +59 -0
  174. package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +80 -0
  175. package/src/migrations/semver.ts +24 -0
  176. package/src/migrations/types.ts +9 -0
  177. package/src/mockOS/components/PermissionModal.tsx +3 -2
  178. package/src/mockOS/components/SubscriptionModal.tsx +400 -0
  179. package/src/mockOS/context/MockOSContext.tsx +61 -10
  180. package/src/mockOS/context/MockOSContextBase.ts +1 -0
  181. package/src/mockOS/hooks/useMockIap.ts +11 -0
  182. package/src/mockOS/index.ts +7 -0
  183. package/src/mockOS/managers/mockOSIapManager.ts +10 -0
  184. package/src/mockOS/managers/subscriptionManager.ts +36 -0
  185. package/src/modals/IconPickerModal.tsx +1 -1
  186. package/src/pages/ProjectDebug.tsx +331 -0
  187. package/src/pages/ProjectMigrationPage.tsx +92 -0
  188. package/src/pages/ProjectPage.tsx +318 -166
  189. package/src/pages/ProjectValidationPage.tsx +54 -0
  190. package/src/styles/base/_global.scss +58 -11
  191. package/src/styles/components/_attributes-editor.scss +1 -1
  192. package/src/styles/components/_bottom-bar.scss +7 -4
  193. package/src/styles/components/_editor-shell.scss +126 -4
  194. package/src/styles/components/_mockos-router.scss +3 -2
  195. package/src/styles/components/_ui-components.scss +10 -5
  196. package/src/styles/foundation/_colors.scss +78 -11
  197. package/src/styles/foundation/_mixins.scss +4 -1
  198. package/src/styles/foundation/_sizes.scss +4 -2
  199. package/src/styles/index.scss +1 -0
  200. package/src/styles/layout/_builder.scss +61 -0
  201. package/src/styles/layout/_project-validation.scss +214 -0
  202. package/src/styles/modals/_add-component.scss +4 -2
  203. package/src/styles/modals/_color-modal.scss +4 -2
  204. package/src/styles/modals/_modal-shell.scss +3 -1
  205. package/src/types/Device.ts +5 -0
  206. package/src/utils/__special_exceptions.ts +88 -0
  207. package/src/utils/analyseNode.ts +8 -2
  208. package/src/utils/analyseNodeByPatterns.ts +43 -9
  209. package/src/utils/extractTextStyle.ts +19 -6
  210. package/src/utils/extractViewStyle.ts +68 -59
  211. package/src/utils/getImage.ts +76 -0
  212. package/src/utils/novaToJson.ts +2 -1
  213. package/src/utils/pasteNode.ts +172 -0
  214. package/src/utils/patterns.ts +4 -3
  215. package/dist/android.svg +0 -43
  216. package/dist/apple.svg +0 -16
  217. package/dist/background.jpg +0 -0
@@ -7,9 +7,28 @@ export default function useNode<
7
7
  const type = node?.type;
8
8
  const defaults = getDefaultsForType(type) as Partial<T> | undefined;
9
9
  if (!defaults) return node;
10
- const mergedAttributes: T = {
11
- ...(defaults as T),
12
- ...((node.attributes as T) ?? ({} as T)),
10
+ const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T & {
11
+ style?: Record<string, unknown>;
12
+ };
13
+ const defaultAttributes = defaults as T as T & {
14
+ style?: Record<string, unknown>;
13
15
  };
16
+ const mergedAttributes: T = {
17
+ ...(defaultAttributes as T),
18
+ ...(nodeAttributes as T),
19
+ // Deep merge `style` so default style values aren't lost when the node provides partial style overrides.
20
+ style: {
21
+ ...(defaultAttributes?.style ?? {}),
22
+ ...(nodeAttributes?.style ?? {}),
23
+ },
24
+ } as T;
25
+ if (
26
+ mergedAttributes &&
27
+ typeof (mergedAttributes as any).style === 'object' &&
28
+ (mergedAttributes as any).style != null &&
29
+ Object.keys((mergedAttributes as any).style).length === 0
30
+ ) {
31
+ delete (mergedAttributes as any).style;
32
+ }
14
33
  return { ...node, attributes: mergedAttributes };
15
34
  }
@@ -26,7 +26,7 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
26
26
  const colorIcon: IconsType = 'colors';
27
27
 
28
28
  const { appConfig, setAppConfig, previewMode, setPreviewMode } =
29
- useRenderStore(s => ({
29
+ useRenderStore((s) => ({
30
30
  appConfig: s.appConfig,
31
31
  setAppConfig: s.setAppConfig,
32
32
  previewMode: s.previewMode,
@@ -53,9 +53,9 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
53
53
  <>
54
54
  <div className={['rb-bottom-bar', className].filter(Boolean).join(' ')}>
55
55
  <button
56
- type='button'
56
+ type="button"
57
57
  className={`rb-bottom-bar__button${themeIsActive ? ' is-active' : ''}`}
58
- aria-label='Theme'
58
+ aria-label="Theme"
59
59
  aria-pressed={themeIsActive}
60
60
  onClick={() =>
61
61
  setAppConfig({
@@ -64,85 +64,85 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
64
64
  })
65
65
  }
66
66
  >
67
- <Icon iconType={themeIcon} size={20} color='currentColor' alt='' />
67
+ <Icon iconType={themeIcon} size={20} color="currentColor" alt="" />
68
68
  </button>
69
69
 
70
70
  <button
71
- type='button'
71
+ type="button"
72
72
  className={`rb-bottom-bar__button rb-bottom-bar__button--rtl${
73
73
  rtlIsActive ? ' is-active' : ''
74
74
  }`}
75
- aria-label='RTL'
75
+ aria-label="RTL"
76
76
  aria-pressed={rtlIsActive}
77
77
  onClick={() =>
78
78
  setAppConfig({ ...appConfig, isRtl: !(appConfig.isRtl ?? false) })
79
79
  }
80
80
  >
81
- <Icon iconType={rtlIcon} size={18} color='currentColor' alt='' />
82
- <span className='rb-bottom-bar__rtl-text'>RTL</span>
81
+ <Icon iconType={rtlIcon} size={18} color="currentColor" alt="" />
82
+ <span className="rb-bottom-bar__rtl-text">RTL</span>
83
83
  </button>
84
84
 
85
85
  <button
86
- type='button'
86
+ type="button"
87
87
  className={`rb-bottom-bar__button rb-bottom-bar__button--preview${
88
88
  previewIsActive ? ' is-active' : ''
89
89
  }`}
90
- aria-label='Magic cursor tool'
90
+ aria-label="Magic cursor tool"
91
91
  aria-pressed={previewIsActive}
92
92
  onClick={() => setPreviewMode(!previewMode)}
93
93
  >
94
94
  <Icon
95
95
  iconType={magicCursorIcon}
96
96
  size={20}
97
- color='currentColor'
98
- alt=''
97
+ color="currentColor"
98
+ alt=""
99
99
  />
100
100
  </button>
101
101
 
102
102
  <button
103
- type='button'
103
+ type="button"
104
104
  className={`rb-bottom-bar__button${isDebugOpen ? ' is-active' : ''}`}
105
- aria-label='Debug'
105
+ aria-label="Debug"
106
106
  aria-pressed={isDebugOpen}
107
107
  onClick={() => setIsDebugOpen(true)}
108
108
  >
109
- <Icon iconType={debugIcon} size={20} color='currentColor' alt='' />
109
+ <Icon iconType={debugIcon} size={20} color="currentColor" alt="" />
110
110
  </button>
111
111
 
112
112
  <button
113
- type='button'
113
+ type="button"
114
114
  className={`rb-bottom-bar__button${
115
115
  isLocalizationOpen ? ' is-active' : ''
116
116
  }`}
117
- aria-label='Localization'
117
+ aria-label="Localization"
118
118
  aria-pressed={isLocalizationOpen}
119
119
  onClick={() => setIsLocalizationOpen(true)}
120
120
  >
121
121
  <Icon
122
122
  iconType={localizationIcon}
123
123
  size={20}
124
- color='currentColor'
125
- alt=''
124
+ color="currentColor"
125
+ alt=""
126
126
  />
127
127
  </button>
128
128
 
129
129
  <button
130
- type='button'
130
+ type="button"
131
131
  className={`rb-bottom-bar__button${isColorsOpen ? ' is-active' : ''}`}
132
- aria-label='Color'
132
+ aria-label="Color"
133
133
  aria-pressed={isColorsOpen}
134
134
  onClick={() => setIsColorsOpen(true)}
135
135
  >
136
- <Icon iconType={colorIcon} size={20} color='currentColor' alt='' />
136
+ <Icon iconType={colorIcon} size={20} color="currentColor" alt="" />
137
137
  </button>
138
138
 
139
- <div className='rb-bottom-bar__spacer' />
139
+ <div className="rb-bottom-bar__spacer" />
140
140
 
141
- <div className='rb-bottom-bar__langs' aria-label='Language'>
142
- {languages.map(language => (
141
+ <div className="rb-bottom-bar__langs" aria-label="Language">
142
+ {languages.map((language) => (
143
143
  <button
144
144
  key={language}
145
- type='button'
145
+ type="button"
146
146
  className={`rb-bottom-bar__lang${
147
147
  activeLanguage === language ? ' is-active' : ''
148
148
  }`}
@@ -175,38 +175,38 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
175
175
  {isDebugOpen && (
176
176
  <Modal
177
177
  onClose={() => setIsDebugOpen(false)}
178
- ariaLabelledBy='debug-json-editor-title'
179
- className='modal--large modal--scrollable'
180
- contentClassName='localication-modal__content'
178
+ ariaLabelledBy="debug-json-editor-title"
179
+ className="modal--large modal--scrollable"
180
+ contentClassName="localication-modal__content"
181
181
  >
182
- <div className='modal__header localication-modal__header'>
183
- <div className='localication-modal__header-main'>
184
- <h3 id='debug-json-editor-title' className='modal__title'>
182
+ <div className="modal__header localication-modal__header">
183
+ <div className="localication-modal__header-main">
184
+ <h3 id="debug-json-editor-title" className="modal__title">
185
185
  Debug JSON
186
186
  </h3>
187
- <p className='localication-modal__description'>
187
+ <p className="localication-modal__description">
188
188
  Inspect and edit raw node JSON.
189
189
  </p>
190
190
  </div>
191
191
  <button
192
- type='button'
193
- className='editor-button'
192
+ type="button"
193
+ className="editor-button"
194
194
  onClick={() => setIsDebugOpen(false)}
195
195
  >
196
196
  Close
197
197
  </button>
198
198
  </div>
199
- <div className='localication-modal__body'>
199
+ <div className="localication-modal__body">
200
200
  <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
201
201
  <Checkbox
202
- label='Preview mode'
202
+ label="Preview mode"
203
203
  checked={previewMode}
204
204
  onChange={setPreviewMode}
205
205
  />
206
206
  <Checkbox
207
- label='Dark Mode'
207
+ label="Dark Mode"
208
208
  checked={appConfig.theme === 'dark'}
209
- onChange={checked =>
209
+ onChange={(checked) =>
210
210
  setAppConfig({
211
211
  ...appConfig,
212
212
  theme: checked ? 'dark' : 'light',
@@ -214,24 +214,24 @@ export function BottomBar({ className, data, setData }: BottomBarProps) {
214
214
  }
215
215
  />
216
216
  <Checkbox
217
- label='Is RTL'
217
+ label="Is RTL"
218
218
  checked={appConfig.isRtl ?? false}
219
- onChange={checked =>
219
+ onChange={(checked) =>
220
220
  setAppConfig({ ...appConfig, isRtl: checked })
221
221
  }
222
222
  />
223
223
  </div>
224
224
  <div
225
- className='localication-modal__editor'
225
+ className="localication-modal__editor"
226
226
  style={{ marginTop: 12 }}
227
227
  >
228
228
  <JsonTextEditor
229
- rootName='node'
229
+ rootName="node"
230
230
  value={data ?? {}}
231
- onChange={next =>
231
+ onChange={(next) =>
232
232
  setData(analyseAndProccess(next as Node) as Node)
233
233
  }
234
- className='localication-modal__json-editor'
234
+ className="localication-modal__json-editor"
235
235
  />
236
236
  </div>
237
237
  </div>
@@ -386,23 +386,37 @@ export function Builder({
386
386
  function createDefaultNode(type: string): NodeData<NodeDefaultAttribute> {
387
387
  const pattern = getPatternByType(type)?.pattern;
388
388
  const defaults = getDefaultsForType(type) ?? {};
389
- let children: Node = '';
390
389
  const childrenSchema = pattern?.children as unknown;
390
+
391
+ // Special-case: CarouselProvider MUST contain a Carousel container inside the viewport
392
+ // otherwise embla-carousel will crash (it expects viewport.firstChild.children).
393
+ if (type === 'CarouselProvider') {
394
+ return {
395
+ type,
396
+ children: createDefaultNode('Carousel'),
397
+ attributes: { ...defaults },
398
+ } as NodeData<NodeDefaultAttribute>;
399
+ }
400
+
401
+ let children: Node = null;
391
402
  if (childrenSchema === 'never') {
392
- children = '';
403
+ children = null;
393
404
  } else if (childrenSchema === 'string') {
394
405
  children = '';
395
406
  } else if (
396
407
  childrenSchema === 'node' ||
397
408
  (Array.isArray(childrenSchema) && childrenSchema.includes('node'))
398
409
  ) {
399
- children = [];
410
+ // Default to "no children yet". Using [] here is truthy and can mount
411
+ // child-dependent widgets (e.g. Embla) with an empty DOM, causing crashes.
412
+ children = null;
400
413
  } else if (typeof childrenSchema === 'string') {
401
- // Specific child type like 'carouselItem' – initialize as empty array to allow multiple
402
- children = [];
414
+ // Specific child type like 'CarouselItem' – seed with one child to match the pattern.
415
+ children = [createDefaultNode(childrenSchema)];
403
416
  } else {
404
- children = '';
417
+ children = null;
405
418
  }
419
+
406
420
  return {
407
421
  type,
408
422
  children,
@@ -1,7 +1,8 @@
1
- import { useRef } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { isNodeNullOrUndefined, isNodeString } from '../utils/analyseNode';
3
3
  import type { Node, NodeData, NodeDefaultAttribute } from '../types/Node';
4
4
  import { getPatternByType } from '../utils/patterns';
5
+ import { Icon } from './Icon.generated';
5
6
 
6
7
  export type BuilderButtonProps = {
7
8
  node: Node;
@@ -26,8 +27,12 @@ export function BuilderButton({
26
27
  }
27
28
  const nodeData = node as NodeData<NodeDefaultAttribute>;
28
29
 
29
- const longPressTimeoutRef = useRef<number | null>(null);
30
- const longPressTriggeredRef = useRef(false);
30
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
31
+ const actionsRef = useRef<HTMLDivElement | null>(null);
32
+ const menuId = useMemo(
33
+ () => `builder-node-actions-${Math.random().toString(36).slice(2, 9)}`,
34
+ [],
35
+ );
31
36
 
32
37
  const handleDelete = () => {
33
38
  if (onDelete) {
@@ -35,34 +40,31 @@ export function BuilderButton({
35
40
  }
36
41
  };
37
42
 
38
- const clearLongPress = () => {
39
- if (longPressTimeoutRef.current !== null) {
40
- window.clearTimeout(longPressTimeoutRef.current);
41
- longPressTimeoutRef.current = null;
42
- }
43
- };
43
+ // Copy/Paste intentionally removed for now.
44
44
 
45
- const handlePressStart = () => {
46
- longPressTriggeredRef.current = false;
47
- longPressTimeoutRef.current = window.setTimeout(() => {
48
- longPressTriggeredRef.current = true;
49
- const shouldDelete = window.confirm('Do you want to delete');
50
- if (shouldDelete) {
51
- handleDelete();
52
- }
53
- }, 600);
54
- };
45
+ useEffect(() => {
46
+ if (!isMenuOpen) return;
55
47
 
56
- const handlePressEnd = () => {
57
- if (!longPressTriggeredRef.current) {
58
- onClick();
59
- }
60
- clearLongPress();
61
- };
48
+ const handlePointerDown = (e: MouseEvent | TouchEvent) => {
49
+ const el = actionsRef.current;
50
+ if (!el) return;
51
+ if (e.target instanceof Element && el.contains(e.target)) return;
52
+ setIsMenuOpen(false);
53
+ };
62
54
 
63
- const handlePressCancel = () => {
64
- clearLongPress();
65
- };
55
+ const handleKeyDown = (e: KeyboardEvent) => {
56
+ if (e.key === 'Escape') setIsMenuOpen(false);
57
+ };
58
+
59
+ document.addEventListener('mousedown', handlePointerDown);
60
+ document.addEventListener('touchstart', handlePointerDown);
61
+ document.addEventListener('keydown', handleKeyDown);
62
+ return () => {
63
+ document.removeEventListener('mousedown', handlePointerDown);
64
+ document.removeEventListener('touchstart', handlePointerDown);
65
+ document.removeEventListener('keydown', handleKeyDown);
66
+ };
67
+ }, [isMenuOpen]);
66
68
 
67
69
  let extra = '';
68
70
  if (nodeData.attributes?.condition) {
@@ -103,17 +105,52 @@ export function BuilderButton({
103
105
  </button>
104
106
  </div>
105
107
  )}
106
- <a
107
- className="builder__button-link"
108
- onMouseDown={handlePressStart}
109
- onMouseUp={handlePressEnd}
110
- onMouseLeave={handlePressCancel}
111
- onTouchStart={handlePressStart}
112
- onTouchEnd={handlePressEnd}
113
- onTouchCancel={handlePressCancel}
114
- >
108
+ <button type="button" className="builder__button-link" onClick={onClick}>
115
109
  {baseLabel}
116
- </a>
110
+ </button>
111
+ <div className="builder__button-actions" ref={actionsRef}>
112
+ <button
113
+ type="button"
114
+ className="builder__button-actions-trigger"
115
+ aria-label="Open node actions"
116
+ aria-haspopup="menu"
117
+ aria-expanded={isMenuOpen}
118
+ aria-controls={menuId}
119
+ onClick={(event) => {
120
+ event.stopPropagation();
121
+ setIsMenuOpen((v) => !v);
122
+ }}
123
+ >
124
+ <Icon iconType="chevron-right" size={16} />
125
+ </button>
126
+ {isMenuOpen && (
127
+ <ul
128
+ id={menuId}
129
+ className="builder__button-actions-menu"
130
+ role="menu"
131
+ aria-label="Node actions"
132
+ >
133
+ <li role="none">
134
+ <button
135
+ type="button"
136
+ className="builder__button-actions-item builder__button-actions-item--danger"
137
+ role="menuitem"
138
+ disabled={!onDelete}
139
+ onClick={(event) => {
140
+ event.stopPropagation();
141
+ if (!onDelete) return;
142
+ const shouldDelete = window.confirm('Do you want to delete?');
143
+ if (!shouldDelete) return;
144
+ handleDelete();
145
+ setIsMenuOpen(false);
146
+ }}
147
+ >
148
+ Delete
149
+ </button>
150
+ </li>
151
+ </ul>
152
+ )}
153
+ </div>
117
154
  {conditionLabel && (
118
155
  <span className="builder__button-condition">{conditionLabel}</span>
119
156
  )}
@@ -1,6 +1,7 @@
1
1
  import React, { createContext, useContext, useMemo } from 'react';
2
2
  import type { Product } from '../paywall/types/paywall-types';
3
3
  import type { PaywallBenefits } from '../paywall/types/benefits';
4
+ import { RenderErrorBoundary } from './RenderErrorBoundary';
4
5
 
5
6
  // NOTE: We keep this context intentionally tiny.
6
7
  // IMPORTANT: This provider may be mounted once but consumed by multiple `build-components`
@@ -12,6 +13,8 @@ export type Products = Product;
12
13
  export type BuilderProviderParams = {
13
14
  products: Products[];
14
15
  benefits: PaywallBenefits;
16
+ onPaywallClose?: () => void;
17
+ onPaywallSubscribe?: (product?: Product) => void | boolean | Promise<boolean>;
15
18
  };
16
19
 
17
20
  type BuilderProviderProps = {
@@ -31,12 +34,29 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
31
34
  params?.benefits && typeof params.benefits === 'object'
32
35
  ? (params.benefits as PaywallBenefits)
33
36
  : {},
37
+ onPaywallClose:
38
+ typeof params?.onPaywallClose === 'function'
39
+ ? params.onPaywallClose
40
+ : undefined,
41
+ onPaywallSubscribe:
42
+ typeof params?.onPaywallSubscribe === 'function'
43
+ ? params.onPaywallSubscribe
44
+ : undefined,
34
45
  }),
35
- [params?.benefits, params?.products],
46
+ [
47
+ params?.benefits,
48
+ params?.products,
49
+ params?.onPaywallClose,
50
+ params?.onPaywallSubscribe,
51
+ ],
36
52
  );
37
53
 
38
54
  return (
39
- <BuilderContext.Provider value={value}>{children}</BuilderContext.Provider>
55
+ <BuilderContext.Provider value={value}>
56
+ <RenderErrorBoundary subtitle="caught by BuilderProvider">
57
+ {children}
58
+ </RenderErrorBoundary>
59
+ </BuilderContext.Provider>
40
60
  );
41
61
  }
42
62
 
@@ -1,11 +1,10 @@
1
1
  import React from 'react';
2
2
  import { Device } from '../types/Device';
3
- import androidIcon from '../assets/images/android.svg';
4
- import iosIcon from '../assets/images/apple.svg';
3
+ import { getImage, TribeAssetName } from '../utils/getImage';
5
4
 
6
5
  const platformIcons: Record<string, string> = {
7
- android: androidIcon,
8
- ios: iosIcon,
6
+ android: getImage(TribeAssetName.Android),
7
+ ios: getImage(TribeAssetName.Apple),
9
8
  };
10
9
 
11
10
  type DeviceButtonProps = {
@@ -20,6 +19,14 @@ export function DeviceButton({
20
19
  onSelect,
21
20
  }: DeviceButtonProps) {
22
21
  const platformIcon = platformIcons[device.platform];
22
+ const aspect =
23
+ device.aspect ??
24
+ (() => {
25
+ const r = device.height / device.width;
26
+ if (r >= 2.05) return 'tall' as const;
27
+ if (r <= 1.75) return 'wide' as const;
28
+ return 'regular' as const;
29
+ })();
23
30
 
24
31
  return (
25
32
  <button
@@ -30,7 +37,7 @@ export function DeviceButton({
30
37
  onClick={() => onSelect(device)}
31
38
  >
32
39
  {device.name} <br />
33
- {device.width}x{device.height}
40
+ {device.width}×{device.height} ({aspect})
34
41
  {platformIcon && <img src={platformIcon} alt="" aria-hidden="true" />}
35
42
  </button>
36
43
  );