@developer_tribe/react-builder 1.2.24 → 1.2.26

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 (193) hide show
  1. package/dist/attributes-editor/AttributesEditorFields.d.ts +1 -1
  2. package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
  3. package/dist/attributes-editor/attributesEditorModelTypes.d.ts +2 -0
  4. package/dist/build-components/BIcon/BIconProps.generated.d.ts +0 -2
  5. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +0 -2
  6. package/dist/build-components/Button/ButtonProps.generated.d.ts +0 -2
  7. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +0 -2
  8. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +0 -2
  9. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +0 -2
  10. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +0 -2
  11. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +0 -2
  12. package/dist/build-components/CountDown/CountDownProps.generated.d.ts +3 -6
  13. package/dist/build-components/Image/ImageProps.generated.d.ts +0 -2
  14. package/dist/build-components/Main/MainProps.generated.d.ts +0 -2
  15. package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +0 -2
  16. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +0 -2
  17. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +0 -2
  18. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +0 -2
  19. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +0 -2
  20. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +0 -2
  21. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +0 -2
  22. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +0 -2
  23. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +0 -2
  24. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +0 -2
  25. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +0 -2
  26. package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +0 -2
  27. package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +0 -2
  28. package/dist/build-components/PaywallOptions/PaywallOptionButton.d.ts +1 -1
  29. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +0 -2
  30. package/dist/build-components/PaywallProvider/PaywallContext.d.ts +1 -2
  31. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +0 -2
  32. package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +0 -2
  33. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +0 -2
  34. package/dist/build-components/Separator/SeparatorProps.generated.d.ts +0 -2
  35. package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +0 -2
  36. package/dist/build-components/Text/TextProps.generated.d.ts +0 -2
  37. package/dist/build-components/index.d.ts +1 -3
  38. package/dist/build-components/patterns.generated.d.ts +818 -1690
  39. package/dist/index.cjs.js +3 -3
  40. package/dist/index.cjs.js.map +1 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.esm.js +4 -4
  43. package/dist/index.esm.js.map +1 -1
  44. package/dist/index.web.cjs.js +4 -4
  45. package/dist/index.web.cjs.js.map +1 -1
  46. package/dist/index.web.esm.js +3 -3
  47. package/dist/index.web.esm.js.map +1 -1
  48. package/dist/pages/ProjectPage.d.ts +2 -2
  49. package/dist/pages/projectPageUtils.d.ts +7 -1
  50. package/dist/paywall/hooks/index.d.ts +0 -1
  51. package/dist/styles.css +1 -1
  52. package/dist/types/Project.d.ts +6 -0
  53. package/dist/utils/index.d.ts +1 -0
  54. package/dist/utils/patterns.d.ts +2 -0
  55. package/dist/utils/projectColors.d.ts +7 -0
  56. package/package.json +3 -3
  57. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +11 -2
  58. package/src/AttributesEditor.tsx +15 -4
  59. package/src/DeviceMockFrame.tsx +0 -2
  60. package/src/RenderPage.tsx +0 -9
  61. package/src/assets/.DS_Store +0 -0
  62. package/src/assets/meta.json +1 -1
  63. package/src/assets/samples/paywall-1.json +0 -1
  64. package/src/assets/samples/paywall-2.json +2 -3
  65. package/src/assets/samples/paywall-app-delete-offer.json +2 -4
  66. package/src/assets/samples/paywall-app-open-offer.json +2 -4
  67. package/src/assets/samples/paywall-back-offer.json +2 -4
  68. package/src/assets/samples/paywall-notification-offer.json +2 -4
  69. package/src/assets/samples/simple-2.json +0 -1
  70. package/src/assets/samples/vpn-onboard-1.json +15 -15
  71. package/src/assets/samples/vpn-onboard-2.json +15 -15
  72. package/src/assets/samples/vpn-onboard-3.json +15 -15
  73. package/src/assets/samples/vpn-onboard-4.json +15 -15
  74. package/src/assets/samples/vpn-onboard-5.json +21 -21
  75. package/src/assets/samples/vpn-onboard-6.json +15 -15
  76. package/src/attributes-editor/AttributesEditorFields.tsx +0 -1
  77. package/src/attributes-editor/AttributesEditorView.tsx +43 -38
  78. package/src/attributes-editor/Field.tsx +1 -1
  79. package/src/attributes-editor/SpecialCategorySection.tsx +5 -4
  80. package/src/attributes-editor/attributesEditorModelTypes.ts +2 -0
  81. package/src/attributes-editor/useAttributesEditorModel.ts +24 -8
  82. package/src/build-components/BIcon/BIcon.tsx +1 -1
  83. package/src/build-components/BIcon/BIconProps.generated.ts +0 -2
  84. package/src/build-components/BIcon/pattern.json +2 -2
  85. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +0 -2
  86. package/src/build-components/BackgroundImage/pattern.json +2 -2
  87. package/src/build-components/Button/ButtonProps.generated.ts +0 -2
  88. package/src/build-components/Button/pattern.json +2 -2
  89. package/src/build-components/Carousel/Carousel.tsx +1 -1
  90. package/src/build-components/Carousel/CarouselProps.generated.ts +0 -2
  91. package/src/build-components/Carousel/pattern.json +3 -3
  92. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +0 -2
  93. package/src/build-components/CarouselButtons/pattern.json +2 -2
  94. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +0 -2
  95. package/src/build-components/CarouselDots/pattern.json +2 -2
  96. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +0 -2
  97. package/src/build-components/CarouselItem/pattern.json +3 -5
  98. package/src/build-components/CarouselProvider/CarouselProvider.tsx +1 -1
  99. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +0 -2
  100. package/src/build-components/CarouselProvider/pattern.json +2 -4
  101. package/src/build-components/CountDown/CountDown.tsx +25 -1
  102. package/src/build-components/CountDown/CountDownProps.generated.ts +3 -6
  103. package/src/build-components/CountDown/pattern.json +10 -2
  104. package/src/build-components/Image/ImageProps.generated.ts +0 -2
  105. package/src/build-components/Image/pattern.json +5 -3
  106. package/src/build-components/Main/MainProps.generated.ts +0 -2
  107. package/src/build-components/Main/pattern.json +2 -2
  108. package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +0 -2
  109. package/src/build-components/NavigationBarColor/pattern.json +2 -2
  110. package/src/build-components/Onboard/OnboardProps.generated.ts +0 -2
  111. package/src/build-components/Onboard/pattern.json +3 -5
  112. package/src/build-components/OnboardButton/OnboardButton.tsx +1 -4
  113. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +0 -2
  114. package/src/build-components/OnboardButton/pattern.json +2 -2
  115. package/src/build-components/OnboardButtons/OnboardButtons.tsx +1 -9
  116. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +0 -2
  117. package/src/build-components/OnboardButtons/pattern.json +2 -2
  118. package/src/build-components/OnboardDot/OnboardDot.tsx +2 -1
  119. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +0 -2
  120. package/src/build-components/OnboardDot/pattern.json +2 -2
  121. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +0 -2
  122. package/src/build-components/OnboardFooter/pattern.json +2 -2
  123. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +0 -2
  124. package/src/build-components/OnboardImage/pattern.json +2 -2
  125. package/src/build-components/OnboardItem/OnboardItem.tsx +1 -1
  126. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +0 -2
  127. package/src/build-components/OnboardItem/pattern.json +2 -2
  128. package/src/build-components/OnboardProvider/OnboardProvider.tsx +0 -1
  129. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +0 -2
  130. package/src/build-components/OnboardProvider/pattern.json +2 -2
  131. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +0 -2
  132. package/src/build-components/OnboardSubtitle/pattern.json +3 -5
  133. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +0 -2
  134. package/src/build-components/OnboardTitle/pattern.json +3 -5
  135. package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +0 -2
  136. package/src/build-components/PaywallBackground/pattern.json +2 -4
  137. package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +0 -2
  138. package/src/build-components/PaywallCloseButton/pattern.json +3 -5
  139. package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +1 -2
  140. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +0 -2
  141. package/src/build-components/PaywallOptions/pattern.json +3 -5
  142. package/src/build-components/PaywallProvider/PaywallContext.ts +1 -1
  143. package/src/build-components/PaywallProvider/PaywallProvider.tsx +0 -10
  144. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +0 -2
  145. package/src/build-components/PaywallProvider/pattern.json +2 -2
  146. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +1 -1
  147. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +0 -2
  148. package/src/build-components/PaywallSubscribeButton/pattern.json +3 -5
  149. package/src/build-components/RadioButton/RadioButton.tsx +1 -1
  150. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +0 -2
  151. package/src/build-components/RadioButton/pattern.json +2 -2
  152. package/src/build-components/RenderNode.generated.tsx +0 -10
  153. package/src/build-components/Separator/SeparatorProps.generated.ts +0 -2
  154. package/src/build-components/Separator/pattern.json +2 -2
  155. package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +0 -2
  156. package/src/build-components/StatusBarColor/pattern.json +2 -2
  157. package/src/build-components/Text/TextProps.generated.ts +0 -2
  158. package/src/build-components/Text/pattern.json +2 -3
  159. package/src/build-components/View/pattern.json +2 -0
  160. package/src/build-components/index.ts +0 -10
  161. package/src/build-components/patterns.generated.ts +850 -1759
  162. package/src/components/AttributesEditorPanel.tsx +48 -32
  163. package/src/components/Builder.tsx +9 -1
  164. package/src/components/BuilderProvider.tsx +2 -37
  165. package/src/hooks/useSafeAreaViewStyle.ts +1 -3
  166. package/src/index.ts +1 -1
  167. package/src/mockOS/managers/navigationManager.ts +1 -1
  168. package/src/pages/ProjectPage.tsx +47 -22
  169. package/src/pages/projectPageUtils.ts +15 -1
  170. package/src/pages/tabs/SideTool.tsx +1 -22
  171. package/src/paywall/hooks/index.ts +0 -1
  172. package/src/store.ts +1 -6
  173. package/src/styles/base/_global.scss +2 -0
  174. package/src/types/Project.ts +7 -0
  175. package/src/utils/analyseNodeByPatterns.ts +4 -0
  176. package/src/utils/index.ts +1 -0
  177. package/src/utils/logRenderStore.ts +0 -1
  178. package/src/utils/novaToJson.ts +1 -5
  179. package/src/utils/parseColor.ts +1 -0
  180. package/src/utils/patterns.ts +2 -0
  181. package/src/utils/projectColors.ts +71 -0
  182. package/dist/build-components/Counter/Counter.d.ts +0 -2
  183. package/dist/build-components/Counter/CounterProps.generated.d.ts +0 -63
  184. package/dist/build-components/PaywallCounter/PaywallCounter.d.ts +0 -2
  185. package/dist/build-components/PaywallCounter/PaywallCounterProps.generated.d.ts +0 -63
  186. package/dist/paywall/hooks/useHandleGoBack.d.ts +0 -1
  187. package/src/build-components/Counter/Counter.tsx +0 -44
  188. package/src/build-components/Counter/CounterProps.generated.ts +0 -80
  189. package/src/build-components/Counter/pattern.json +0 -25
  190. package/src/build-components/PaywallCounter/PaywallCounter.tsx +0 -46
  191. package/src/build-components/PaywallCounter/PaywallCounterProps.generated.ts +0 -80
  192. package/src/build-components/PaywallCounter/pattern.json +0 -23
  193. package/src/paywall/hooks/useHandleGoBack.ts +0 -60
@@ -1,3 +1,4 @@
1
+ import { useCallback, useRef } from 'react';
1
2
  import { AttributesEditor } from '../AttributesEditor';
2
3
  import type { Node, NodeData } from '../types/Node';
3
4
  import type { ProjectColors } from '../types/Project';
@@ -11,6 +12,32 @@ interface AttributesEditorPanelProps {
11
12
  projectColors?: ProjectColors;
12
13
  }
13
14
 
15
+ function replaceNode(root: Node, target: Node, next: Node): Node {
16
+ if (root === target) return next;
17
+ if (root === null || root === undefined) return root;
18
+ if (typeof root === 'string') return root;
19
+ if (Array.isArray(root)) {
20
+ let changed = false;
21
+ const arr = root.map((item) => {
22
+ const r = replaceNode(item, target, next);
23
+ if (r !== item) changed = true;
24
+ return r;
25
+ });
26
+ return changed ? arr : root;
27
+ }
28
+ const data = root as NodeData;
29
+ if ('children' in data) {
30
+ const prev = data.children;
31
+ const replaced = Array.isArray(prev)
32
+ ? prev.map((c: Node) => replaceNode(c, target, next))
33
+ : replaceNode(prev as Node, target, next);
34
+ if (replaced !== prev) {
35
+ return { ...data, children: replaced } as Node;
36
+ }
37
+ }
38
+ return root;
39
+ }
40
+
14
41
  export function AttributesEditorPanel({
15
42
  attributes,
16
43
  onChange,
@@ -21,6 +48,27 @@ export function AttributesEditorPanel({
21
48
  current: s.current,
22
49
  setCurrent: s.setCurrent,
23
50
  }));
51
+
52
+ // Stable refs so the onChange callback doesn't change identity every render.
53
+ const attributesRef = useRef(attributes);
54
+ attributesRef.current = attributes;
55
+ const currentRef = useRef(current);
56
+ currentRef.current = current;
57
+ const onChangeRef = useRef(onChange);
58
+ onChangeRef.current = onChange;
59
+
60
+ const handleAttributesChange = useCallback(
61
+ (next: Node) => {
62
+ const root = attributesRef.current as Node;
63
+ const target = currentRef.current;
64
+ if (!target) return;
65
+ const updated = replaceNode(root, target, next);
66
+ onChangeRef.current(updated);
67
+ setCurrent(next);
68
+ },
69
+ [setCurrent],
70
+ );
71
+
24
72
  if (!current) return null;
25
73
 
26
74
  const currentKey =
@@ -33,38 +81,6 @@ export function AttributesEditorPanel({
33
81
  : null;
34
82
  const nodeForEditor = resolvedCurrent ?? current;
35
83
 
36
- function replaceNode(root: Node, target: Node, next: Node): Node {
37
- if (root === target) return next;
38
- if (root === null || root === undefined) return root;
39
- if (typeof root === 'string') return root;
40
- if (Array.isArray(root)) {
41
- let changed = false;
42
- const arr = root.map((item) => {
43
- const r = replaceNode(item, target, next);
44
- if (r !== item) changed = true;
45
- return r;
46
- });
47
- return changed ? arr : root;
48
- }
49
- const data = root as NodeData;
50
- if ('children' in data) {
51
- const prev = data.children;
52
- const replaced = Array.isArray(prev)
53
- ? prev.map((c: Node) => replaceNode(c, target, next))
54
- : replaceNode(prev as Node, target, next);
55
- if (replaced !== prev) {
56
- return { ...data, children: replaced } as Node;
57
- }
58
- }
59
- return root;
60
- }
61
- const handleAttributesChange = (next: Node) => {
62
- const root = attributes as Node;
63
- const updated = replaceNode(root, current, next);
64
- onChange(updated);
65
- setCurrent(next);
66
- };
67
-
68
84
  return (
69
85
  <div className="attributes-editor-panel">
70
86
  <AttributesEditor
@@ -330,11 +330,13 @@ export function Builder({
330
330
  setData(updatedRoot);
331
331
  setCurrent(updatedParent);
332
332
  },
333
+ // eslint-disable-next-line react-hooks/exhaustive-deps
333
334
  [current, data, setData, setCurrent, usedKeys],
334
335
  );
335
336
 
336
337
  const allowedChildTypes = useMemo(
337
338
  () => getAllowedChildTypes(current),
339
+ // eslint-disable-next-line react-hooks/exhaustive-deps
338
340
  [current],
339
341
  );
340
342
  const parentType = useMemo(() => {
@@ -378,6 +380,7 @@ export function Builder({
378
380
  setCurrent(next);
379
381
  }
380
382
  },
383
+ // eslint-disable-next-line react-hooks/exhaustive-deps
381
384
  [current, data, setCurrent, setData],
382
385
  );
383
386
 
@@ -407,6 +410,7 @@ export function Builder({
407
410
  setCurrent(updatedParent);
408
411
  }
409
412
  },
413
+ // eslint-disable-next-line react-hooks/exhaustive-deps
410
414
  [current, data, setCurrent, setData],
411
415
  );
412
416
 
@@ -436,6 +440,7 @@ export function Builder({
436
440
  setCurrent(updatedParent);
437
441
  }
438
442
  },
443
+ // eslint-disable-next-line react-hooks/exhaustive-deps
439
444
  [current, data, setCurrent, setData],
440
445
  );
441
446
 
@@ -603,7 +608,10 @@ function getNodeLabel(node: Node): string {
603
608
  if (isNodeNullOrUndefined(node)) return 'Empty';
604
609
  if (isNodeString(node)) return node as string;
605
610
  if (isNodeArray(node)) return 'Collection';
606
- return (node as NodeData<NodeDefaultAttribute>).type ?? 'Node';
611
+ const nodeData = node as NodeData<NodeDefaultAttribute>;
612
+ const title = (nodeData.attributes as Record<string, unknown>)?.title;
613
+ if (typeof title === 'string' && title.trim().length > 0) return title;
614
+ return nodeData.type ?? 'Node';
607
615
  }
608
616
 
609
617
  function findNodePath(root: Node, target: Node): Node[] {
@@ -4,6 +4,7 @@ import type { PaywallBenefits } from '../paywall/types/benefits';
4
4
  import type { AppConfig } from '../types/PreviewConfig';
5
5
  import type { Fonts } from '../types/Fonts';
6
6
  import type { ProjectColors } from '../types/Project';
7
+ import { defaultProjectColors, mergeProjectColors } from '../utils';
7
8
 
8
9
  // NOTE: We keep this context intentionally tiny.
9
10
  // IMPORTANT: This provider may be mounted once but consumed by multiple `build-components`
@@ -65,7 +66,7 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
65
66
  : undefined,
66
67
  projectColors:
67
68
  params?.projectColors && typeof params.projectColors === 'object'
68
- ? (params.projectColors as ProjectColors)
69
+ ? mergeProjectColors(defaultProjectColors, params.projectColors)
69
70
  : defaultProjectColors,
70
71
  fonts: Array.isArray(params?.fonts) ? (params.fonts as Fonts) : undefined,
71
72
  appFont: params?.appFont,
@@ -95,42 +96,6 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
95
96
  );
96
97
  }
97
98
 
98
- const defaultProjectColors: Readonly<ProjectColors> = {
99
- STATIC_COLORS: {
100
- BLACK: '#000',
101
- WHITE: '#FFFFFF',
102
- TRANSPARENT: '#ffffff00',
103
- ONBOARD_DOT_INACTIVE: '#E4E5E7',
104
- ONBOARD_DOT_ACTIVE: '#007AFF',
105
- BUTTON_PRIMARY_BACKGROUND: '#0066FF',
106
- BUTTON_PRIMARY_TEXT: '#FFFFFF',
107
- LINK_COLOR: '#1778F2',
108
- SEPARATOR_COLOR: '#44454D',
109
- },
110
- THEME_COLORS: {
111
- light: {
112
- TEXT: '#161827',
113
- BACKGROUND: '#F4F5FF',
114
- ICON: '#0450E2',
115
- LINE: '#E9EBF9',
116
- ONBOARD_TITLE: '#161827',
117
- ONBOARD_SUBTITLE: '#44454D',
118
- BUTTON_SECONDARY_TEXT: '#81838F',
119
- FOOTER_TEXT: '#81838F',
120
- },
121
- dark: {
122
- TEXT: '#E9EBF9',
123
- BACKGROUND: '#080A17',
124
- ICON: '#0450E2',
125
- LINE: '#161827',
126
- ONBOARD_TITLE: '#FDFDFD',
127
- ONBOARD_SUBTITLE: '#C7C7C7',
128
- BUTTON_SECONDARY_TEXT: '#A9AAAC',
129
- FOOTER_TEXT: '#A2A4B1',
130
- },
131
- },
132
- } as const;
133
-
134
99
  export function useBuilderParams(): Readonly<BuilderProviderParams> {
135
100
  return (
136
101
  useContext(builderContext) ?? {
@@ -33,9 +33,7 @@ export function useSafeAreaViewStyle(
33
33
  return useMemo(() => {
34
34
  if (!enabled) return baseStyle;
35
35
 
36
- const [insetTop, insetRight, insetBottom, insetLeft] = device?.insets ?? [
37
- 0, 0, 0, 0,
38
- ];
36
+ const [insetTop, insetRight, , insetLeft] = device?.insets ?? [0, 0, 0, 0];
39
37
 
40
38
  // Match DeviceMockFrame fallbacks: status bar overlays content, so we treat it as top safe area.
41
39
  const top =
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  // Types
14
14
  export type { TargetedScreenSize } from './types/TargetedScreenSize';
15
15
  export type { Node, NodeData, NodeDefaultAttribute } from './types/Node';
16
- export type { Project, ProjectColors } from './types/Project';
16
+ export type { Project, ProjectColors, ProjectMeta } from './types/Project';
17
17
  export type { Device } from './types/Device';
18
18
  export type { AppConfig, Localication } from './types/PreviewConfig';
19
19
  export { defaultAppConfig } from './types/PreviewConfig';
@@ -70,7 +70,7 @@ export class MockNavigationManager {
70
70
  }
71
71
 
72
72
  if (this.stack.length > 0) {
73
- const popped = this.stack.pop();
73
+ this.stack.pop();
74
74
  return true;
75
75
  }
76
76
 
@@ -1,6 +1,6 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import type { Node, NodeData } from '../types/Node';
3
- import type { Project, ProjectColors } from '../types/Project';
3
+ import type { Project, ProjectColors, ProjectMeta } from '../types/Project';
4
4
  import { ToastContainer, toast } from 'react-toastify';
5
5
  import { RenderPage } from '../RenderPage';
6
6
  import { EditorHeader } from '../components/EditorHeader';
@@ -31,16 +31,16 @@ import {
31
31
  deleteNodeFromTree,
32
32
  findNodeByKey,
33
33
  isNodeRecord,
34
- nodeHasChild,
35
34
  } from '../utils/nodeTree';
36
35
  import type { Fonts } from '../types/Fonts';
37
36
  import { useProjectFonts } from '../hooks/useProjectFonts';
38
- import { resolveProjectForSave } from './projectPageUtils';
37
+ import { resolveProjectForSave, toProjectMeta } from './projectPageUtils';
39
38
  import { getDefaultProject } from '../utils/getDefaultProject';
40
39
  import { CURRENT_PROJECT_VERSION } from '../migrations/migratePipe';
41
40
  export type ProjectPageProps = {
42
41
  project: Project;
43
- onSaveProject: (project: Project) => void;
42
+ // TODO: Tüm onSaveProject call-site'ları toProjectMeta kullanacak şekilde migrate et
43
+ onSaveProject: (project: ProjectMeta) => void;
44
44
  appConfig?: AppConfig;
45
45
  logLevel?: LogLevel;
46
46
  projectColors?: ProjectColors;
@@ -151,16 +151,18 @@ export function ProjectPage({
151
151
  return;
152
152
  }
153
153
 
154
- if (isNodeRecord(current) && nodeHasChild(current, nodeToDelete)) {
155
- const currentKey = current.key;
156
- if (currentKey) {
157
- const nextCurrent = findNodeByKey(updated, currentKey);
158
- setCurrent(nextCurrent ?? updated);
159
- } else {
160
- setCurrent(updated);
161
- }
154
+ // Refresh current's reference so the Builder re-renders the updated subtree.
155
+ // deleteNodeFromTree creates new objects for ancestors of the removed node,
156
+ // so the old `current` reference becomes stale when the deleted node was a
157
+ // descendant of `current`.
158
+ if (isNodeRecord(current) && current.key) {
159
+ const nextCurrent = findNodeByKey(updated, current.key);
160
+ setCurrent(nextCurrent ?? updated);
161
+ } else {
162
+ setCurrent(updated);
162
163
  }
163
164
  },
165
+ // eslint-disable-next-line react-hooks/exhaustive-deps
164
166
  [editorData, current],
165
167
  );
166
168
 
@@ -224,10 +226,29 @@ export function ProjectPage({
224
226
  setMinLoadingDelayDone(false);
225
227
  const timer = setTimeout(() => setMinLoadingDelayDone(true), 1000);
226
228
  return () => clearTimeout(timer);
229
+ // eslint-disable-next-line react-hooks/exhaustive-deps
227
230
  }, [activeProject.data]);
228
231
 
232
+ // Ref for the full project (used inside effect for migration check etc.)
233
+ const activeProjectRef = useRef(activeProject);
234
+ activeProjectRef.current = activeProject;
235
+ // Ref for current editorData so the effect can compare without depending on it.
236
+ const editorDataRef = useRef(editorData);
237
+ editorDataRef.current = editorData;
238
+
229
239
  useEffect(() => {
230
240
  try {
241
+ const currentProject = activeProjectRef.current;
242
+ const projectData = currentProject.data;
243
+
244
+ // Guard: skip reinit when the incoming project data is the same reference
245
+ // we already hold in editorData. After a save round-trip the parent pushes
246
+ // a new project object whose .data is the very same reference as editorData.
247
+ // Re-processing it would flash a loading state and discard in-flight changes.
248
+ if (projectData != null && projectData === editorDataRef.current) {
249
+ return;
250
+ }
251
+
231
252
  // Reset to "loading" immediately on project change so the loader is shown
232
253
  // until a valid node is available (and for at least 2 seconds).
233
254
  if (!isEmptyProjectData) {
@@ -237,7 +258,7 @@ export function ProjectPage({
237
258
  setValidationError(null);
238
259
  setValidationErrorStack(null);
239
260
  // Version gate: if project is older than the current schema, show migration UI.
240
- const pipe = getMigrationPipe(activeProject);
261
+ const pipe = getMigrationPipe(currentProject);
241
262
  if (!bypassValidation && pipe.required) {
242
263
  setMigrationGate(pipe);
243
264
  setEditorData(null);
@@ -248,8 +269,8 @@ export function ProjectPage({
248
269
  if (bypassValidation) {
249
270
  // Best-effort: let the user continue with the raw data even if invalid.
250
271
  // This may still crash the preview, but it unblocks users for debugging.
251
- setEditorData(activeProject.data as Node);
252
- setCurrent(activeProject.data as Node);
272
+ setEditorData(currentProject.data as Node);
273
+ setCurrent(currentProject.data as Node);
253
274
  return;
254
275
  }
255
276
  if (isEmptyProjectData) {
@@ -258,7 +279,7 @@ export function ProjectPage({
258
279
  return;
259
280
  }
260
281
 
261
- const inputNode: Node = activeProject.data as Node;
282
+ const inputNode: Node = currentProject.data as Node;
262
283
 
263
284
  const processed = analyseAndProccess(inputNode);
264
285
  if (!processed) return;
@@ -275,7 +296,9 @@ export function ProjectPage({
275
296
  setEditorData(null);
276
297
  setCurrent(null);
277
298
  }
278
- }, [activeProject, activeProject.data, bypassValidation, setCurrent]);
299
+ // Note: depend on activeProject.data (not activeProject object) to avoid
300
+ // reinit when the project wrapper changes but data is the same reference.
301
+ }, [activeProject.data, isEmptyProjectData, bypassValidation, setCurrent]);
279
302
 
280
303
  const showLoading =
281
304
  !isEmptyProjectData && (editorData === null || !minLoadingDelayDone);
@@ -300,13 +323,15 @@ export function ProjectPage({
300
323
  if (onSaveProjectColors && resolvedProjectColors) {
301
324
  onSaveProjectColors(resolvedProjectColors);
302
325
  }
303
- onSaveProject(
326
+ const projectMeta = toProjectMeta(
304
327
  resolveProjectForSave({
305
328
  project,
306
329
  overrideProject,
307
330
  data: editorData,
308
331
  }),
309
332
  );
333
+ logger.info('ProjectPage', 'saving project meta', projectMeta);
334
+ onSaveProject(projectMeta);
310
335
  toast.success('Saved');
311
336
  } catch (e) {
312
337
  logger.error('ProjectPage', 'save project failed', e);
@@ -377,7 +402,7 @@ export function ProjectPage({
377
402
 
378
403
  const { project: migratedProject } =
379
404
  runProjectMigrations(projectForMigration);
380
- onSaveProject(migratedProject);
405
+ onSaveProject(toProjectMeta(migratedProject));
381
406
  setOverrideProject(migratedProject);
382
407
  setBypassValidation(true);
383
408
  setMigrationGate(null);
@@ -430,7 +455,7 @@ export function ProjectPage({
430
455
 
431
456
  // This action only fixes project metadata. It intentionally does NOT
432
457
  // validate/normalize node data (it might still be invalid).
433
- onSaveProject(fixedProject);
458
+ onSaveProject(toProjectMeta(fixedProject));
434
459
  setOverrideProject(fixedProject);
435
460
  setBypassValidation(false);
436
461
  setMigrationGate(null);
@@ -488,7 +513,7 @@ export function ProjectPage({
488
513
  data: nodeCandidate as Project['data'],
489
514
  };
490
515
 
491
- onSaveProject(nextProject);
516
+ onSaveProject(toProjectMeta(nextProject));
492
517
  setOverrideProject(nextProject);
493
518
  setBypassValidation(false);
494
519
  setValidationError(null);
@@ -1,4 +1,4 @@
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
  import { getDefaultProject } from '../utils/getDefaultProject';
4
4
 
@@ -13,3 +13,17 @@ export function resolveProjectForSave(args: {
13
13
  data: args.data,
14
14
  });
15
15
  }
16
+
17
+ /**
18
+ * Strips a full Project down to its essential persistence fields.
19
+ * Use before handing the project to onSaveProject so consumers only
20
+ * receive the canonical metadata (name, version, type, data).
21
+ */
22
+ export function toProjectMeta(project: Project): ProjectMeta {
23
+ return {
24
+ name: project.name,
25
+ version: project.version,
26
+ ...(project.type ? { type: project.type } : {}),
27
+ data: project.data,
28
+ };
29
+ }
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { Modal } from '../../modals';
3
3
  import type { Node } from '../../types/Node';
4
4
  import type { Localication } from '../../types/PreviewConfig';
@@ -52,12 +52,6 @@ export function SideTool({ data, setData }: SideToolProps) {
52
52
  useLogRender('SideTool');
53
53
  const [isDebugModalOpen, setIsDebugModalOpen] = useState(false);
54
54
  const [isLocalicationModalOpen, setIsLocalicationModalOpen] = useState(false);
55
- const [isCompactMode, setIsCompactMode] = useState(() => {
56
- if (typeof window === 'undefined') {
57
- return false;
58
- }
59
- return window.innerWidth < 1000;
60
- });
61
55
  const [isCompactPanelVisible, setIsCompactPanelVisible] = useState(false);
62
56
  const { appConfig, setAppConfig, previewMode, setPreviewMode } =
63
57
  useRenderStore((s) => ({
@@ -67,21 +61,6 @@ export function SideTool({ data, setData }: SideToolProps) {
67
61
  setPreviewMode: s.setPreviewMode,
68
62
  }));
69
63
 
70
- useEffect(() => {
71
- if (typeof window === 'undefined') {
72
- return;
73
- }
74
-
75
- const handleResize = () => {
76
- const compact = window.innerWidth < 1000;
77
- setIsCompactMode(compact);
78
- };
79
-
80
- handleResize();
81
- window.addEventListener('resize', handleResize);
82
- return () => window.removeEventListener('resize', handleResize);
83
- }, []);
84
-
85
64
  const getScreenColorValue = (mode: ScreenMode, key: ScreenColorKey) =>
86
65
  appConfig.screenStyle?.[mode]?.[key] ?? screenStyleDefaults[mode][key];
87
66
 
@@ -1,5 +1,4 @@
1
1
  export { useCalculateLocalizedPrice } from './useCalculateLocalizedPrice';
2
2
  export { useDiscountRate } from './useDiscountRate';
3
3
  export { useChangeDelayByPaywall } from './useChangeDelayByPaywall';
4
- export { useHandleGoBack } from './useHandleGoBack';
5
4
  export { useMockOSBackHandler } from './useMockOSBackHandler';
package/src/store.ts CHANGED
@@ -1,13 +1,8 @@
1
1
  import { createWithEqualityFn } from 'zustand/traditional';
2
2
  import { shallow } from 'zustand/shallow';
3
3
  import type { Device } from './types/Device';
4
- import {
5
- defaultAppConfig,
6
- type AppConfig,
7
- type Localication,
8
- } from './types/PreviewConfig';
4
+ import { defaultAppConfig, type AppConfig } from './types/PreviewConfig';
9
5
  import { getDefaultDevice } from './utils/getDevices';
10
- import { ScreenStyle } from './RenderPage';
11
6
  import { createJSONStorage, persist } from 'zustand/middleware';
12
7
  import { Node } from './types/Node';
13
8
  import type { LogEntry, LogLevel, ProjectColors } from './types/Project';
@@ -61,6 +61,8 @@ body,
61
61
  min-width: 200px;
62
62
  border-right: 1px solid colors.$borderColor;
63
63
  overflow: auto;
64
+ max-height: calc(100vh - 120px);
65
+ padding-bottom: 80px;
64
66
  }
65
67
 
66
68
  .split-right {
@@ -29,6 +29,13 @@ export interface ProjectBase<T> {
29
29
 
30
30
  export interface Project extends ProjectBase<Node> {}
31
31
 
32
+ /**
33
+ * Lightweight subset of Project containing only the essential metadata
34
+ * needed for persistence (name, version, type, data).
35
+ * Excludes runtime/editor-only fields like appConfig and projectColors.
36
+ */
37
+ export type ProjectMeta = Pick<Project, 'name' | 'version' | 'type' | 'data'>;
38
+
32
39
  export type LogLevel = 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE';
33
40
 
34
41
  export type LogSource = string;
@@ -344,6 +344,10 @@ function validateAttributeValue(
344
344
  // null to clear fields (e.g. events[].navigate_to = null).
345
345
  if (value == null) return ok();
346
346
 
347
+ // "never" type means the attribute is marked as not to be validated.
348
+ // Skip validation for attributes with "never" type.
349
+ if (spec === 'never') return ok();
350
+
347
351
  if (Array.isArray(spec)) {
348
352
  return validateEnumValue(value, spec, path);
349
353
  }
@@ -0,0 +1 @@
1
+ export { defaultProjectColors, mergeProjectColors } from './projectColors';
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  // This file is a debug utility specifically designed to log to console for debugging purposes.
3
2
  import { useRenderStore } from '../store';
4
3
 
@@ -1,5 +1,5 @@
1
1
  import { Node, NodeData } from '../types/Node';
2
- import { Project, ProjectBase } from '../types/Project';
2
+ import { ProjectBase } from '../types/Project';
3
3
  //TODO: deprecated olmasına rağmen snapshot testi ekle
4
4
  /**
5
5
  * @deprecated Legacy converter for old "nova" onboard JSON formats.
@@ -226,7 +226,6 @@ function buildCarouselItem(
226
226
  const flex = attrs?.flex ? Number(attrs.flex) : undefined;
227
227
 
228
228
  // Normalize events and compute Navigate target indices when resolvable
229
- let targetIndex: number | undefined = undefined;
230
229
  //@eslint-disable-next-line @typescript-eslint/no-explicit-any
231
230
  const actions = (attrs?.actions || []) as any[];
232
231
  const normalizedEvents: {
@@ -263,9 +262,6 @@ function buildCarouselItem(
263
262
  ? { targetIndex: pageKeyToIndex.get(nextKey!)! }
264
263
  : {}),
265
264
  });
266
- if (hasTarget) {
267
- targetIndex = pageKeyToIndex.get(nextKey!)!;
268
- }
269
265
  }
270
266
  }
271
267
  }
@@ -19,6 +19,7 @@ export function parseColor(value?: string, options: ParseColorOptions = {}) {
19
19
  if (trimmed.startsWith(STATIC_PREFIX)) {
20
20
  const token = trimmed.slice(STATIC_PREFIX.length);
21
21
  const resolved = projectColors.STATIC_COLORS?.[token];
22
+ console.log('resolved', value, resolved);
22
23
  return typeof resolved === 'string' && resolved.trim()
23
24
  ? resolved.trim()
24
25
  : trimmed;
@@ -48,6 +48,8 @@ type Pattern = {
48
48
  schemaVersion: number;
49
49
  pattern: {
50
50
  type: string;
51
+ title?: string;
52
+ description?: string;
51
53
  children: unknown;
52
54
  attributes?: Record<string, string | string[]>;
53
55
  };
@@ -0,0 +1,71 @@
1
+ import type {
2
+ ProjectColors,
3
+ ProjectColorTokenMap,
4
+ ProjectThemeColors,
5
+ } from '../types/Project';
6
+
7
+ export const defaultProjectColors: ProjectColors = {
8
+ STATIC_COLORS: {
9
+ BLACK: '#000',
10
+ WHITE: '#FFFFFF',
11
+ TRANSPARENT: '#ffffff00',
12
+ ONBOARD_DOT_INACTIVE: '#E4E5E7',
13
+ ONBOARD_DOT_ACTIVE: '#007AFF',
14
+ ONBOARD_BUTTON_PRIMARY_BACKGROUND: '#0000FF',
15
+ ONBOARD_BUTTON_PRIMARY_TEXT: '#000',
16
+ ONBOARD_LINK_COLOR: '#1778F2',
17
+ ONBOARD_SEPARATOR_COLOR: '#44454D',
18
+ },
19
+ THEME_COLORS: {
20
+ light: {
21
+ TEXT: '#161827',
22
+ BACKGROUND: '#F4F5FF',
23
+ ICON: '#0450E2',
24
+ LINE: '#E9EBF9',
25
+ ONBOARD_TITLE: '#161827',
26
+ ONBOARD_SUBTITLE: '#44454D',
27
+ ONBOARD_BUTTON_SECONDARY_TEXT: '#81838F',
28
+ ONBOARD_FOOTER_TEXT: '#81838F',
29
+ },
30
+ dark: {
31
+ TEXT: '#E9EBF9',
32
+ BACKGROUND: '#080A17',
33
+ ICON: '#0450E2',
34
+ LINE: '#161827',
35
+ ONBOARD_TITLE: '#FDFDFD',
36
+ ONBOARD_SUBTITLE: '#C7C7C7',
37
+ ONBOARD_BUTTON_SECONDARY_TEXT: '#A9AAAC',
38
+ ONBOARD_FOOTER_TEXT: '#A2A4B1',
39
+ },
40
+ },
41
+ };
42
+
43
+ /**
44
+ * Merges custom project colors with base colors.
45
+ * Custom colors override base colors on a per-token basis.
46
+ */
47
+ export function mergeProjectColors(
48
+ baseColors: ProjectColors,
49
+ customColors: ProjectColors,
50
+ ): ProjectColors {
51
+ const staticColors: ProjectColorTokenMap = {
52
+ ...baseColors.STATIC_COLORS,
53
+ ...(customColors.STATIC_COLORS || {}),
54
+ };
55
+
56
+ const themeColors: ProjectThemeColors = {
57
+ light: {
58
+ ...(baseColors.THEME_COLORS?.light || {}),
59
+ ...(customColors.THEME_COLORS?.light || {}),
60
+ },
61
+ dark: {
62
+ ...(baseColors.THEME_COLORS?.dark || {}),
63
+ ...(customColors.THEME_COLORS?.dark || {}),
64
+ },
65
+ };
66
+
67
+ return {
68
+ STATIC_COLORS: staticColors,
69
+ THEME_COLORS: themeColors,
70
+ };
71
+ }
@@ -1,2 +0,0 @@
1
- import type { CounterComponentProps } from './CounterProps.generated';
2
- export declare function Counter({ node }: CounterComponentProps): import("react/jsx-runtime").JSX.Element;