@developer_tribe/react-builder 1.2.15 → 1.2.18

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 (87) hide show
  1. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +1 -1
  2. package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -1
  3. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +1 -1
  4. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +1 -1
  5. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +1 -1
  6. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -1
  7. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -1
  8. package/dist/build-components/Image/ImageProps.generated.d.ts +1 -1
  9. package/dist/build-components/Main/MainProps.generated.d.ts +1 -1
  10. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -1
  11. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +1 -1
  12. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +1 -1
  13. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +1 -1
  14. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +1 -1
  15. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +1 -1
  16. package/dist/build-components/PaywallCloseButton/PaywallCloseButton.d.ts +1 -1
  17. package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
  18. package/dist/build-components/PaywallProvider/PaywallContext.d.ts +2 -1
  19. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +2 -1
  20. package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +1 -1
  21. package/dist/build-components/Text/TextProps.generated.d.ts +1 -1
  22. package/dist/build-components/View/ViewProps.generated.d.ts +0 -2
  23. package/dist/build-components/patterns.generated.d.ts +49 -41
  24. package/dist/index.cjs.js +3 -3
  25. package/dist/index.cjs.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.esm.js +4 -4
  28. package/dist/index.esm.js.map +1 -1
  29. package/dist/index.web.cjs.js +3 -3
  30. package/dist/index.web.cjs.js.map +1 -1
  31. package/dist/index.web.esm.js +3 -3
  32. package/dist/index.web.esm.js.map +1 -1
  33. package/dist/mockOS/backHandler.d.ts +4 -0
  34. package/dist/paywall/hooks/index.d.ts +3 -3
  35. package/dist/paywall/hooks/useChangeDelayByPaywall.d.ts +4 -0
  36. package/dist/paywall/hooks/useHandleGoBack.d.ts +1 -0
  37. package/dist/paywall/hooks/useMockOSBackHandler.d.ts +1 -0
  38. package/package.json +4 -1
  39. package/src/RenderPage.tsx +0 -24
  40. package/src/assets/meta.json +1 -1
  41. package/src/assets/samples/paywall-2.json +3 -2
  42. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +1 -1
  43. package/src/build-components/Button/ButtonProps.generated.ts +1 -1
  44. package/src/build-components/Carousel/CarouselProps.generated.ts +1 -1
  45. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +1 -1
  46. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +1 -1
  47. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -1
  48. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -1
  49. package/src/build-components/CountDown/CountDown.tsx +9 -1
  50. package/src/build-components/Image/ImageProps.generated.ts +1 -1
  51. package/src/build-components/Main/MainProps.generated.ts +1 -1
  52. package/src/build-components/Onboard/OnboardProps.generated.ts +1 -1
  53. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +1 -1
  54. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +1 -1
  55. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +1 -1
  56. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +1 -1
  57. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +1 -1
  58. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +5 -1
  59. package/src/build-components/PaywallCloseButton/pattern.json +3 -0
  60. package/src/build-components/PaywallCounter/PaywallCounter.tsx +2 -5
  61. package/src/build-components/PaywallOptions/PaywallOptions.tsx +2 -2
  62. package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
  63. package/src/build-components/PaywallProvider/PaywallContext.ts +4 -2
  64. package/src/build-components/PaywallProvider/PaywallProvider.tsx +45 -7
  65. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +2 -1
  66. package/src/build-components/PaywallProvider/pattern.json +11 -1
  67. package/src/build-components/RadioButton/RadioButtonProps.generated.ts +1 -1
  68. package/src/build-components/Text/TextProps.generated.ts +1 -1
  69. package/src/build-components/View/ViewProps.generated.ts +0 -2
  70. package/src/build-components/View/pattern.json +0 -2
  71. package/src/build-components/patterns.generated.ts +50 -41
  72. package/src/components/BuilderProvider.tsx +0 -9
  73. package/src/components/DeviceNavigationBar.tsx +5 -0
  74. package/src/index.ts +1 -3
  75. package/src/mockOS/backHandler.ts +35 -0
  76. package/src/mockOS/managers/navigationManager.ts +0 -3
  77. package/src/pages/ProjectMigrationPage.tsx +3 -1
  78. package/src/paywall/hooks/index.ts +3 -3
  79. package/src/paywall/hooks/useChangeDelayByPaywall.ts +25 -0
  80. package/src/paywall/hooks/useHandleGoBack.ts +60 -0
  81. package/src/paywall/hooks/useMockOSBackHandler.ts +9 -0
  82. package/dist/paywall/hooks/useCarouselOptionsSeperator.d.ts +0 -6
  83. package/dist/paywall/hooks/useCloseStatusPaywall.d.ts +0 -4
  84. package/dist/paywall/hooks/usePaywallCounter.d.ts +0 -4
  85. package/src/paywall/hooks/useCarouselOptionsSeperator.ts +0 -8
  86. package/src/paywall/hooks/useCloseStatusPaywall.ts +0 -6
  87. package/src/paywall/hooks/usePaywallCounter.ts +0 -6
@@ -0,0 +1,4 @@
1
+ export type MockOSBackHandler = () => boolean;
2
+ export declare function registerMockOSBackHandler(handler: MockOSBackHandler): () => void;
3
+ export declare function emitMockOSBackPress(): boolean;
4
+ export declare function clearMockOSBackHandlers(): void;
@@ -1,5 +1,5 @@
1
- export { usePaywallCounter } from './usePaywallCounter';
2
- export { useCloseStatusPaywall } from './useCloseStatusPaywall';
3
- export { useCarouselOptionsSeperator } from './useCarouselOptionsSeperator';
4
1
  export { useCalculateLocalizedPrice } from './useCalculateLocalizedPrice';
5
2
  export { useDiscountRate } from './useDiscountRate';
3
+ export { useChangeDelayByPaywall } from './useChangeDelayByPaywall';
4
+ export { useHandleGoBack } from './useHandleGoBack';
5
+ export { useMockOSBackHandler } from './useMockOSBackHandler';
@@ -0,0 +1,4 @@
1
+ import type { NodeData } from '../../types/Node';
2
+ export declare function useChangeDelayByPaywall(node: NodeData<{
3
+ delay?: number;
4
+ }> | null | undefined, setIsBackAllowed: (value: boolean) => void): void;
@@ -0,0 +1 @@
1
+ export declare function useHandleGoBack(handleGoBack: () => boolean | null | undefined): void;
@@ -0,0 +1 @@
1
+ export declare function useMockOSBackHandler(isBackAllowed: boolean): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@developer_tribe/react-builder",
3
- "version": "1.2.15",
3
+ "version": "1.2.18",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "restricted": true,
@@ -33,6 +33,9 @@
33
33
  ],
34
34
  "scripts": {
35
35
  "prebuild": "node ./scripts/prebuild/prebuild.js",
36
+ "prebuild:basics": "yarn builder build --name builder --components=View --components=Text --components=Button --components=Image --components=Carousel --components=CarouselProvider --components=CarouselItem --components=CarouselDots --components=CarouselButtons --components=BIcon --components=BackgroundImage --components=Main --components=RadioButton --components=Counter --components=CountDown",
37
+ "prebuild:onboard": "yarn builder build --name onboard/builder --components=OnboardProvider --components=Onboard --components=OnboardItem --components=OnboardImage --components=OnboardTitle --components=OnboardSubtitle --components=OnboardDot --components=OnboardButtons --components=OnboardButton --components=OnboardFooter",
38
+ "prebuild:paywall": "yarn builder build --name paywall/builder --components=PaywallProvider --components=PaywallBackground --components=PaywallCloseButton --components=PaywallOptions --components=PaywallSubscribeButton --components=PaywallCounter",
36
39
  "build": "yarn prebuild && yarn rimraf dist && rollup -c",
37
40
  "build:watch": "rollup -c -w",
38
41
  "lint": "eslint src",
@@ -84,60 +84,37 @@ export function RenderPage({
84
84
  const showEmptyState = isEmptyPreview(data);
85
85
 
86
86
  useEffect(() => {
87
- console.info('[preview] RenderPage effect', {
88
- previewMode,
89
- forceRender,
90
- hasRoot: !!previewRootRef.current,
91
- });
92
-
93
87
  if (!previewMode) {
94
88
  return;
95
89
  }
96
90
 
97
91
  const root = previewRootRef.current;
98
92
  if (!root) {
99
- console.info('[preview] RenderPage effect skipped (no root)');
100
93
  return;
101
94
  }
102
95
 
103
- console.info('[preview] RenderPage effect bound', {
104
- rootTag: root.tagName,
105
- rootClass: root.className,
106
- });
107
-
108
96
  const handleClick = (event: MouseEvent) => {
109
97
  const target = event.target as HTMLElement | null;
110
- console.info('[preview] click', {
111
- target: target ? target.tagName : null,
112
- className: target ? target.className : null,
113
- });
114
98
 
115
99
  if (!target) return;
116
100
 
117
101
  // Ignore clicks on carousel dots to avoid interfering with navigation
118
102
  if (target.closest('.embla__dot')) {
119
- console.info('[preview] click ignored (embla dot)');
120
103
  return;
121
104
  }
122
105
 
123
106
  const element = target.closest('[attribute-key]') as HTMLElement | null;
124
107
  if (!element) {
125
- console.info('[preview] click ignored (no attribute-key ancestor)');
126
108
  return;
127
109
  }
128
110
 
129
111
  const key = element.getAttribute('attribute-key');
130
112
 
131
113
  if (!key) {
132
- console.info('[preview] click ignored (no attribute-key value)');
133
114
  return;
134
115
  }
135
116
 
136
117
  const node = findNodeByKeyNested(data, key);
137
- console.info('[preview] click resolved', {
138
- key,
139
- foundNode: !!node,
140
- });
141
118
  setCurrent(node);
142
119
  onSelectNode?.(node);
143
120
  };
@@ -145,7 +122,6 @@ export function RenderPage({
145
122
  root.addEventListener('click', handleClick);
146
123
 
147
124
  return () => {
148
- console.info('[preview] RenderPage effect cleanup');
149
125
  root.removeEventListener('click', handleClick);
150
126
  };
151
127
  }, [previewMode, data, onSelectNode, setCurrent, forceRender]); // forceRender: retrigger effect when we want to force a refresh (e.g. route change)
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "supportedProjectVersion": "1.1.2",
3
- "reactBuilderVersion": "1.2.15"
3
+ "reactBuilderVersion": "1.2.17"
4
4
  }
@@ -37,6 +37,7 @@
37
37
  "backgroundColor": "#EEF2FF"
38
38
  },
39
39
  "scrollable": true,
40
+ "delay": 5000,
40
41
  "description": "Paywall provider container.",
41
42
  "title": "Main Paywall"
42
43
  },
@@ -293,14 +294,14 @@
293
294
  {
294
295
  "type": "CountDown",
295
296
  "attributes": {
296
- "count": 30000,
297
+ "count": 10000,
297
298
  "style": {
298
299
  "color": "#FF0000",
299
300
  "fontWeight": "700",
300
301
  "fontSize": 40,
301
302
  "textAlign": "center"
302
303
  },
303
- "description": "Countdown timer (30 seconds).",
304
+ "description": "Countdown timer (10 seconds).",
304
305
  "title": "Countdown"
305
306
  },
306
307
  "children": null
@@ -59,9 +59,9 @@ export interface BackgroundImagePropsGenerated {
59
59
  child: string;
60
60
  attributes: {
61
61
  style?: BackgroundImageStyleGenerated;
62
+ scrollable?: boolean;
62
63
  title?: string;
63
64
  description?: string;
64
- scrollable?: boolean;
65
65
  src?: string;
66
66
  resizeMode?: ResizeModeOptionType;
67
67
  };
@@ -73,9 +73,9 @@ export interface ButtonPropsGenerated {
73
73
  child: string;
74
74
  attributes: {
75
75
  style?: ButtonStyleGenerated;
76
+ scrollable?: boolean;
76
77
  title?: string;
77
78
  description?: string;
78
- scrollable?: boolean;
79
79
  };
80
80
  }
81
81
 
@@ -59,9 +59,9 @@ export interface CarouselPropsGenerated {
59
59
  child: string;
60
60
  attributes: {
61
61
  style?: CarouselStyleGenerated;
62
+ scrollable?: boolean;
62
63
  title?: string;
63
64
  description?: string;
64
- scrollable?: boolean;
65
65
  loop?: boolean;
66
66
  dragFree?: boolean;
67
67
  align?: AlignOptionType;
@@ -62,9 +62,9 @@ export interface CarouselButtonsPropsGenerated {
62
62
  child: string;
63
63
  attributes: {
64
64
  style?: CarouselButtonsStyleGenerated;
65
+ scrollable?: boolean;
65
66
  title?: string;
66
67
  description?: string;
67
- scrollable?: boolean;
68
68
  buttonType?: ButtonTypeOptionType;
69
69
  skipNumber?: number;
70
70
  };
@@ -65,9 +65,9 @@ export interface CarouselDotsPropsGenerated {
65
65
  child: string;
66
66
  attributes: {
67
67
  style?: CarouselDotsStyleGenerated;
68
+ scrollable?: boolean;
68
69
  title?: string;
69
70
  description?: string;
70
- scrollable?: boolean;
71
71
  dotType?: DotTypeOptionType;
72
72
  };
73
73
  }
@@ -58,9 +58,9 @@ export interface CarouselItemPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: CarouselItemStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  };
65
65
  }
66
66
 
@@ -58,9 +58,9 @@ export interface CarouselProviderPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: CarouselProviderStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  };
65
65
  }
66
66
 
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useId, useState } from 'react';
1
+ import React, { useEffect, useId, useRef, useState } from 'react';
2
2
  import type { CountDownComponentProps } from './CountDownProps.generated';
3
3
  import useNode from '../useNode';
4
4
  import { useBuilderParams } from '../../components/BuilderProvider';
@@ -7,6 +7,7 @@ import { useLogRender } from '../../utils/useLogRender';
7
7
  import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
8
8
  import { useMergedStyle } from '../../utils/useMergedStyle';
9
9
  import { formatCountdownTime } from './formatCountdownTime';
10
+ import { usePaywallContext } from '../PaywallProvider/PaywallContext';
10
11
 
11
12
  const DEFAULT_FORMAT = 'm : s';
12
13
 
@@ -20,6 +21,8 @@ export function CountDown({ node }: CountDownComponentProps) {
20
21
  const attributeKey = node.key ?? generatedId;
21
22
 
22
23
  const { previewMode, selectedKey } = useBuilderParams();
24
+ const { onCounterDown } = usePaywallContext();
25
+ const onCounterDownRef = useRef(onCounterDown);
23
26
  const baseStyle = useExtractTextStyle(node);
24
27
  const isSelected = isNodeSelected({
25
28
  previewMode: !!previewMode,
@@ -39,6 +42,10 @@ export function CountDown({ node }: CountDownComponentProps) {
39
42
 
40
43
  const [time, setTime] = useState<string>('');
41
44
 
45
+ useEffect(() => {
46
+ onCounterDownRef.current = onCounterDown;
47
+ }, [onCounterDown]);
48
+
42
49
  useEffect(() => {
43
50
  const targetTimeMs = Date.now() + delayMs;
44
51
  setTime(formatCountdownTime(targetTimeMs, DEFAULT_FORMAT));
@@ -53,6 +60,7 @@ export function CountDown({ node }: CountDownComponentProps) {
53
60
  setTime(formatCountdownTime(targetTimeMs, DEFAULT_FORMAT, nowMs));
54
61
  if (diffMs <= 0) {
55
62
  clearInterval(intervalId);
63
+ onCounterDownRef.current?.();
56
64
  }
57
65
  }, 950); // Keep slightly under 1s to reduce visible drift.
58
66
 
@@ -59,9 +59,9 @@ export interface ImagePropsGenerated {
59
59
  child: string;
60
60
  attributes: {
61
61
  style?: ImageStyleGenerated;
62
+ scrollable?: boolean;
62
63
  title?: string;
63
64
  description?: string;
64
- scrollable?: boolean;
65
65
  src?: string;
66
66
  width?: string;
67
67
  height?: string;
@@ -58,9 +58,9 @@ export interface MainPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: MainStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  useSafeAreaView?: boolean;
65
65
  };
66
66
  }
@@ -58,9 +58,9 @@ export interface OnboardPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: OnboardStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  };
65
65
  }
66
66
 
@@ -73,9 +73,9 @@ export interface OnboardButtonPropsGenerated {
73
73
  child: string;
74
74
  attributes: {
75
75
  style?: OnboardButtonStyleGenerated;
76
+ scrollable?: boolean;
76
77
  title?: string;
77
78
  description?: string;
78
- scrollable?: boolean;
79
79
  labelKey?: string;
80
80
  button_text_color?: string;
81
81
  animation?: AnimationOptionType;
@@ -64,9 +64,9 @@ export interface OnboardButtonsPropsGenerated {
64
64
  child: string;
65
65
  attributes: {
66
66
  style?: OnboardButtonsStyleGenerated;
67
+ scrollable?: boolean;
67
68
  title?: string;
68
69
  description?: string;
69
- scrollable?: boolean;
70
70
  buttonType?: ButtonTypeOptionType;
71
71
  skipNumber?: number;
72
72
  buttons_direction?: ButtonsDirectionOptionType;
@@ -65,9 +65,9 @@ export interface OnboardDotPropsGenerated {
65
65
  child: string;
66
66
  attributes: {
67
67
  style?: OnboardDotStyleGenerated;
68
+ scrollable?: boolean;
68
69
  title?: string;
69
70
  description?: string;
70
- scrollable?: boolean;
71
71
  dotType?: DotTypeOptionType;
72
72
  inactive_dot_opacity?: number;
73
73
  expanding_dot_width?: string;
@@ -59,9 +59,9 @@ export interface OnboardItemPropsGenerated {
59
59
  child: string;
60
60
  attributes: {
61
61
  style?: OnboardItemStyleGenerated;
62
+ scrollable?: boolean;
62
63
  title?: string;
63
64
  description?: string;
64
- scrollable?: boolean;
65
65
  display?: DisplayOptionType;
66
66
  gap?: string;
67
67
  flexDirection?: FlexDirectionOptionType;
@@ -59,9 +59,9 @@ export interface OnboardProviderPropsGenerated {
59
59
  child: string;
60
60
  attributes: {
61
61
  style?: OnboardProviderStyleGenerated;
62
+ scrollable?: boolean;
62
63
  title?: string;
63
64
  description?: string;
64
- scrollable?: boolean;
65
65
  theme?: ThemeOptionType;
66
66
  borderRadius?: never;
67
67
  };
@@ -14,7 +14,7 @@ import { usePaywallContext } from '../PaywallProvider/PaywallContext';
14
14
  function PaywallCloseButton({ node }: PaywallCloseButtonComponentProps) {
15
15
  useLogRender('PaywallCloseButton');
16
16
  node = useNode(node);
17
- const { onClose } = usePaywallContext();
17
+ const { onClose, isBackAllowed } = usePaywallContext();
18
18
 
19
19
  const generatedId = useId();
20
20
  const attributeName = node.sourceType ?? node.type ?? 'PaywallCloseButton';
@@ -35,6 +35,10 @@ function PaywallCloseButton({ node }: PaywallCloseButtonComponentProps) {
35
35
  isSelected ? SELECTED_OUTLINE_STYLE : undefined,
36
36
  );
37
37
 
38
+ if (!isBackAllowed) {
39
+ return null;
40
+ }
41
+
38
42
  const attrs = node.attributes;
39
43
  const styleBag = attrs?.style;
40
44
  const iconType = attrs?.iconType ?? 'close';
@@ -21,6 +21,9 @@
21
21
  "iconType": "close",
22
22
  "size": 24,
23
23
  "style": {
24
+ "position": "absolute",
25
+ "top": "35@vs",
26
+ "left": "24@s",
24
27
  "flexDirection": "row",
25
28
  "justifyContent": "center",
26
29
  "alignItems": "center"
@@ -6,7 +6,6 @@ import { useExtractTextStyle } from '../../attribute-analyser/style/web/useExtra
6
6
  import { useLogRender } from '../../utils/useLogRender';
7
7
  import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
8
8
  import { useMergedStyle } from '../../utils/useMergedStyle';
9
- import { usePaywallCounter } from '../../paywall/hooks';
10
9
 
11
10
  //Optimzation trade off by readability: skip React.memo to keep named exports.
12
11
  export function PaywallCounter({ node }: PaywallCounterComponentProps) {
@@ -29,11 +28,9 @@ export function PaywallCounter({ node }: PaywallCounterComponentProps) {
29
28
  isSelected ? SELECTED_OUTLINE_STYLE : undefined,
30
29
  );
31
30
 
32
- const paywallCount = usePaywallCounter();
33
31
  const fallbackCount = node.attributes?.count;
34
- const count = Number.isFinite(paywallCount)
35
- ? paywallCount
36
- : typeof fallbackCount === 'number' && Number.isFinite(fallbackCount)
32
+ const count =
33
+ typeof fallbackCount === 'number' && Number.isFinite(fallbackCount)
37
34
  ? fallbackCount
38
35
  : 0;
39
36
 
@@ -52,7 +52,7 @@ function PaywallOptions({ node }: PaywallOptionsComponentProps) {
52
52
  useLogRender('PaywallOptions');
53
53
  node = useNode(node);
54
54
  const { products } = useBuilderParams();
55
- const { selectedProductId, setSelectedProductId } = usePaywallContext();
55
+ const { selectedProduct, setSelectedProductId } = usePaywallContext();
56
56
  const getParamsForProduct = usePaywallOptionParamsFactory();
57
57
 
58
58
  const sortedProducts = useMemo(() => {
@@ -67,7 +67,7 @@ function PaywallOptions({ node }: PaywallOptionsComponentProps) {
67
67
  <>
68
68
  {sortedProducts.map((p, index) => {
69
69
  const productId = p.productId || `${index}`;
70
- const isSelected = selectedProductId === productId;
70
+ const isSelected = selectedProduct?.productId === productId;
71
71
  const { localizationParams, otherParams } = getParamsForProduct(p, {
72
72
  isSelected,
73
73
  });
@@ -58,9 +58,9 @@ export interface PaywallOptionsPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: PaywallOptionsStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  };
65
65
  }
66
66
 
@@ -3,11 +3,12 @@ import type { Product } from '../../paywall/types/paywall-types';
3
3
 
4
4
  export type PaywallContextValue = {
5
5
  products: Product[];
6
- selectedProductId: string;
7
6
  setSelectedProductId: (productId: string) => void;
8
7
  selectedProduct?: Product;
9
8
  onClose?: () => void;
10
9
  onSubscribe?: (product?: Product) => void | boolean | Promise<boolean>;
10
+ onCounterDown?: () => void;
11
+ isBackAllowed: boolean;
11
12
  };
12
13
 
13
14
  export const PaywallContext = createContext<PaywallContextValue | undefined>(
@@ -18,8 +19,9 @@ export function usePaywallContext(): PaywallContextValue {
18
19
  return (
19
20
  useContext(PaywallContext) ?? {
20
21
  products: [],
21
- selectedProductId: '',
22
22
  setSelectedProductId: () => {},
23
+ onCounterDown: () => {},
24
+ isBackAllowed: true,
23
25
  }
24
26
  );
25
27
  }
@@ -11,6 +11,10 @@ import { LocalizationParamsProvider } from '../../components/LocalizationParamsP
11
11
  import { useBuilderParams } from '../../components/BuilderProvider';
12
12
  import { PaywallContext } from './PaywallContext';
13
13
  import { useMockOSContext } from '../../mockOS/context/MockOSContextBase';
14
+ import type { Product } from '../../paywall/types/paywall-types';
15
+ import { useChangeDelayByPaywall } from '../../paywall/hooks/useChangeDelayByPaywall';
16
+ import { useHandleGoBack } from '../../paywall/hooks/useHandleGoBack';
17
+ import { useMockOSBackHandler } from '../../paywall/hooks/useMockOSBackHandler';
14
18
 
15
19
  function PaywallProvider({ node }: PaywallProviderComponentProps) {
16
20
  useLogRender('PaywallProvider');
@@ -57,6 +61,7 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
57
61
  );
58
62
 
59
63
  const [selectedProductId, setSelectedProductId] = useState<string>('');
64
+ const [isBackAllowed, setIsBackAllowed] = useState<boolean>(false);
60
65
  useEffect(() => {
61
66
  const list = Array.isArray(products) ? products : [];
62
67
  if (list.length === 0) {
@@ -70,6 +75,9 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
70
75
  }
71
76
  }, [products, selectedProductId]);
72
77
 
78
+ useChangeDelayByPaywall(node, setIsBackAllowed);
79
+ useMockOSBackHandler(isBackAllowed);
80
+
73
81
  const selectedProduct = useMemo(() => {
74
82
  const list = Array.isArray(products) ? products : [];
75
83
  return (
@@ -79,6 +87,9 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
79
87
  }, [products, selectedProductId]);
80
88
 
81
89
  const handleClose = useCallback(() => {
90
+ if (!isBackAllowed) {
91
+ return;
92
+ }
82
93
  // Default: in MockOS go back (simulate dismissing paywall screen).
83
94
  // TODO: at react native merge it will change
84
95
  if (mockOS?.isEnabled) {
@@ -87,13 +98,22 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
87
98
  mockOS.navigation('launchscreen');
88
99
  }
89
100
  }
90
- }, [mockOS]);
101
+ }, [mockOS, isBackAllowed]);
102
+
103
+ const handleGoBack = useCallback(() => {
104
+ if (!isBackAllowed) {
105
+ return false;
106
+ }
107
+ return true;
108
+ }, [isBackAllowed]);
109
+
110
+ useHandleGoBack(handleGoBack);
91
111
 
92
112
  const handleSubscribe = useCallback(
93
- async (product?: { productId?: string }) => {
113
+ async (product?: Product): Promise<boolean> => {
94
114
  // Host app override wins.
95
115
  if (onPaywallSubscribe) {
96
- const result = onPaywallSubscribe(product as any);
116
+ const result = onPaywallSubscribe(product);
97
117
  const ok =
98
118
  result instanceof Promise
99
119
  ? await result
@@ -105,7 +125,7 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
105
125
  if (ok && mockOS?.isEnabled) {
106
126
  mockOS.goBack?.();
107
127
  }
108
- return;
128
+ return ok;
109
129
  }
110
130
 
111
131
  // Default: in MockOS show a native-like subscription prompt.
@@ -122,26 +142,44 @@ function PaywallProvider({ node }: PaywallProviderComponentProps) {
122
142
  // TODO: at react native merge it will change
123
143
  mockOS.goBack?.();
124
144
  }
145
+ return ok;
125
146
  }
147
+ return false;
126
148
  },
127
149
  [mockOS, onPaywallSubscribe],
128
150
  );
129
151
 
152
+ const handleCounterDown = useCallback(() => {
153
+ if (!isBackAllowed) {
154
+ return;
155
+ }
156
+ // Default: in MockOS go back (simulate dismissing paywall screen).
157
+ // TODO: at react native merge it will change
158
+ if (mockOS?.isEnabled) {
159
+ const canGoBack = mockOS.goBack();
160
+ if (!canGoBack) {
161
+ mockOS.navigation('launchscreen');
162
+ }
163
+ }
164
+ }, [isBackAllowed, mockOS]);
165
+
130
166
  const paywallContextValue = useMemo(
131
167
  () => ({
132
168
  products: Array.isArray(products) ? products : [],
133
- selectedProductId,
134
169
  setSelectedProductId,
135
170
  selectedProduct,
136
171
  onClose: handleClose,
137
- onSubscribe: handleSubscribe as any,
172
+ onSubscribe: handleSubscribe,
173
+ onCounterDown: handleCounterDown,
174
+ isBackAllowed,
138
175
  }),
139
176
  [
140
177
  products,
141
- selectedProductId,
142
178
  selectedProduct,
143
179
  handleClose,
144
180
  handleSubscribe,
181
+ handleCounterDown,
182
+ isBackAllowed,
145
183
  ],
146
184
  );
147
185
 
@@ -58,9 +58,10 @@ export interface PaywallProviderPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: PaywallProviderStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
+ delay?: number;
64
65
  };
65
66
  }
66
67
 
@@ -6,7 +6,8 @@
6
6
  "extends": "View",
7
7
  "attributes": {
8
8
  "title": "title",
9
- "description": "description"
9
+ "description": "description",
10
+ "delay": "number"
10
11
  }
11
12
  },
12
13
  "defaults": {
@@ -21,6 +22,15 @@
21
22
  ],
22
23
  "label": "Paywall Provider",
23
24
  "description": "Provider/wrapper for paywall screen components.",
25
+ "attributes": {
26
+ "delay": {
27
+ "label": "Back Delay (ms)",
28
+ "description": "Milliseconds before the paywall can be dismissed via back/close.",
29
+ "category": "other",
30
+ "specialCategory": null,
31
+ "sort": 1
32
+ }
33
+ },
24
34
  "mockableFeatures": {
25
35
  "products": true,
26
36
  "benefits": true
@@ -58,9 +58,9 @@ export interface RadioButtonPropsGenerated {
58
58
  child: string;
59
59
  attributes: {
60
60
  style?: RadioButtonStyleGenerated;
61
+ scrollable?: boolean;
61
62
  title?: string;
62
63
  description?: string;
63
- scrollable?: boolean;
64
64
  selected?: boolean;
65
65
  color?: string;
66
66
  size?: number;