@developer_tribe/react-builder 1.2.43 → 1.2.44-test.2

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 (115) hide show
  1. package/dist/attributes-editor/Field.d.ts +3 -1
  2. package/dist/attributes-editor/attributesEditorModelTypes.d.ts +3 -0
  3. package/dist/attributes-editor/useAttributesEditorModel.d.ts +1 -1
  4. package/dist/build-components/Checkbox/Checkbox.d.ts +6 -0
  5. package/dist/build-components/Checkbox/CheckboxProps.generated.d.ts +67 -0
  6. package/dist/build-components/FormCheckbox/FormCheckbox.d.ts +3 -0
  7. package/dist/build-components/FormCheckbox/FormCheckboxProps.generated.d.ts +69 -0
  8. package/dist/build-components/FormErrorText/FormErrorText.d.ts +3 -0
  9. package/dist/build-components/FormErrorText/FormErrorTextProps.generated.d.ts +61 -0
  10. package/dist/build-components/FormProvider/FormProvider.d.ts +11 -0
  11. package/dist/build-components/FormProvider/FormProviderProps.generated.d.ts +55 -0
  12. package/dist/build-components/FormSubmitButton/FormSubmitButton.d.ts +2 -0
  13. package/dist/build-components/FormSubmitButton/FormSubmitButtonProps.generated.d.ts +78 -0
  14. package/dist/build-components/GlobalProvider/GlobalContext.d.ts +28 -0
  15. package/dist/build-components/GlobalProvider/GlobalProvider.d.ts +5 -0
  16. package/dist/build-components/GlobalProvider/GlobalProviderProps.generated.d.ts +60 -0
  17. package/dist/build-components/GlobalProvider/globalProviderUtils.d.ts +28 -0
  18. package/dist/build-components/GlobalProvider/useGlobalNavigation.d.ts +19 -0
  19. package/dist/build-components/GlobalProvider/useGlobalProviderLogic.d.ts +15 -0
  20. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +22 -10
  21. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +2 -0
  22. package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +2 -0
  23. package/dist/build-components/SystemButton/SystemButton.d.ts +7 -0
  24. package/dist/build-components/SystemButton/SystemButtonProps.generated.d.ts +76 -0
  25. package/dist/build-components/SystemButton/usePlacementButtonEvents.d.ts +35 -0
  26. package/dist/build-components/TermsProvider/TermsProvider.d.ts +5 -0
  27. package/dist/build-components/TermsProvider/TermsProviderProps.generated.d.ts +55 -0
  28. package/dist/build-components/WebView/WebView.d.ts +2 -0
  29. package/dist/build-components/WebView/WebViewProps.generated.d.ts +59 -0
  30. package/dist/build-components/index.d.ts +10 -1
  31. package/dist/build-components/patterns.generated.d.ts +5645 -1686
  32. package/dist/index.cjs.js +1 -1
  33. package/dist/index.cjs.js.map +1 -1
  34. package/dist/index.esm.js +1 -1
  35. package/dist/index.esm.js.map +1 -1
  36. package/dist/index.web.cjs.js +5 -5
  37. package/dist/index.web.cjs.js.map +1 -1
  38. package/dist/index.web.d.ts +1 -0
  39. package/dist/index.web.esm.js +4 -4
  40. package/dist/index.web.esm.js.map +1 -1
  41. package/dist/mockOS/context/MockOSContextBase.d.ts +3 -1
  42. package/dist/styles.css +1 -1
  43. package/dist/types/PreviewConfig.d.ts +1 -1
  44. package/dist/utils/nodeTree.d.ts +18 -0
  45. package/package.json +2 -1
  46. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +68 -4
  47. package/src/assets/meta.json +1 -1
  48. package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
  49. package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
  50. package/src/assets/samples/getSamples.ts +7 -0
  51. package/src/assets/samples/global-onboard-flow.json +735 -0
  52. package/src/assets/samples/terms-and-privacy-no-form.json +108 -0
  53. package/src/assets/samples/terms-and-privacy.json +130 -0
  54. package/src/attributes-editor/AttributesEditorView.tsx +3 -0
  55. package/src/attributes-editor/Field.tsx +91 -2
  56. package/src/attributes-editor/attributesEditorModelTypes.ts +3 -0
  57. package/src/attributes-editor/useAttributesEditorModel.ts +8 -0
  58. package/src/build-components/Checkbox/Checkbox.tsx +165 -0
  59. package/src/build-components/Checkbox/CheckboxProps.generated.ts +84 -0
  60. package/src/build-components/Checkbox/pattern.json +83 -0
  61. package/src/build-components/FormCheckbox/FormCheckbox.tsx +108 -0
  62. package/src/build-components/FormCheckbox/FormCheckboxProps.generated.ts +86 -0
  63. package/src/build-components/FormCheckbox/pattern.json +39 -0
  64. package/src/build-components/FormErrorText/FormErrorText.tsx +34 -0
  65. package/src/build-components/FormErrorText/FormErrorTextProps.generated.ts +78 -0
  66. package/src/build-components/FormErrorText/pattern.json +21 -0
  67. package/src/build-components/FormProvider/FormProvider.tsx +131 -0
  68. package/src/build-components/FormProvider/FormProviderProps.generated.ts +72 -0
  69. package/src/build-components/FormProvider/pattern.json +33 -0
  70. package/src/build-components/FormSubmitButton/FormSubmitButton.tsx +52 -0
  71. package/src/build-components/FormSubmitButton/FormSubmitButtonProps.generated.ts +114 -0
  72. package/src/build-components/FormSubmitButton/pattern.json +33 -0
  73. package/src/build-components/GlobalProvider/GlobalContext.ts +48 -0
  74. package/src/build-components/GlobalProvider/GlobalProvider.tsx +51 -0
  75. package/src/build-components/GlobalProvider/GlobalProviderProps.generated.ts +78 -0
  76. package/src/build-components/GlobalProvider/globalProviderUtils.ts +204 -0
  77. package/src/build-components/GlobalProvider/pattern.json +55 -0
  78. package/src/build-components/GlobalProvider/useGlobalNavigation.ts +65 -0
  79. package/src/build-components/GlobalProvider/useGlobalProviderLogic.ts +172 -0
  80. package/src/build-components/OnboardButton/OnboardButton.tsx +44 -36
  81. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +40 -10
  82. package/src/build-components/OnboardButton/pattern.json +7 -4
  83. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +12 -0
  84. package/src/build-components/OnboardProvider/pattern.json +9 -1
  85. package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +12 -0
  86. package/src/build-components/PaywallProvider/pattern.json +9 -1
  87. package/src/build-components/RenderNode.generated.tsx +46 -1
  88. package/src/build-components/SystemButton/SystemButton.tsx +74 -0
  89. package/src/build-components/SystemButton/SystemButtonProps.generated.ts +112 -0
  90. package/src/build-components/SystemButton/pattern.json +63 -0
  91. package/src/build-components/SystemButton/usePlacementButtonEvents.ts +114 -0
  92. package/src/build-components/TermsProvider/TermsProvider.tsx +45 -0
  93. package/src/build-components/TermsProvider/TermsProviderProps.generated.ts +82 -0
  94. package/src/build-components/TermsProvider/pattern.json +35 -0
  95. package/src/build-components/WebView/WebView.tsx +149 -0
  96. package/src/build-components/WebView/WebViewProps.generated.ts +76 -0
  97. package/src/build-components/WebView/pattern.json +71 -0
  98. package/src/build-components/index.ts +45 -0
  99. package/src/build-components/patterns.generated.ts +5735 -1557
  100. package/src/components/AttributesEditorPanel.tsx +1 -0
  101. package/src/index.web.ts +3 -0
  102. package/src/mockOS/components/MockOSRouter.tsx +21 -0
  103. package/src/mockOS/context/MockOSContext.tsx +7 -0
  104. package/src/mockOS/context/MockOSContextBase.ts +4 -0
  105. package/src/patterns/event-constants.json +19 -0
  106. package/src/styles/components/_checkbox.scss +19 -0
  107. package/src/styles/components/_global-provider.scss +131 -0
  108. package/src/styles/components/_webview.scss +52 -0
  109. package/src/styles/index.scss +4 -0
  110. package/src/types/PreviewConfig.ts +19 -0
  111. package/src/utils/analyseNodeByPatterns.ts +5 -2
  112. package/src/utils/nodeTree.ts +115 -0
  113. package/src/utils/projectColors.ts +4 -0
  114. package/src/.DS_Store +0 -0
  115. package/src/assets/.DS_Store +0 -0
@@ -0,0 +1,72 @@
1
+ /* AUTO-GENERATED FILE - DO NOT EDIT */
2
+
3
+ import type { NodeData } from '../../types/Node';
4
+
5
+ export type FlexDirectionOptionType = 'row' | 'column';
6
+ export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
7
+ export type AlignItemsOptionType =
8
+ | 'flex-start'
9
+ | 'center'
10
+ | 'flex-end'
11
+ | 'stretch'
12
+ | 'baseline';
13
+ export type JustifyContentOptionType =
14
+ | 'flex-start'
15
+ | 'center'
16
+ | 'flex-end'
17
+ | 'space-between'
18
+ | 'space-around'
19
+ | 'space-evenly';
20
+ export type PositionOptionType = 'relative' | 'absolute';
21
+
22
+ export interface FormProviderStyleGenerated {
23
+ flexDirection?: FlexDirectionOptionType;
24
+ flexWrap?: FlexWrapOptionType;
25
+ alignItems?: AlignItemsOptionType;
26
+ justifyContent?: JustifyContentOptionType;
27
+ gap?: string;
28
+ padding?: string;
29
+ paddingHorizontal?: string;
30
+ paddingVertical?: string;
31
+ paddingTop?: string;
32
+ paddingBottom?: string;
33
+ paddingLeft?: string;
34
+ paddingRight?: string;
35
+ margin?: string;
36
+ marginHorizontal?: string;
37
+ marginVertical?: string;
38
+ marginTop?: string;
39
+ marginBottom?: string;
40
+ marginLeft?: string;
41
+ marginRight?: string;
42
+ backgroundColor?: string;
43
+ borderRadius?: string;
44
+ width?: string;
45
+ minWidth?: string;
46
+ maxWidth?: string;
47
+ height?: string;
48
+ minHeight?: string;
49
+ maxHeight?: string;
50
+ flex?: number;
51
+ position?: PositionOptionType;
52
+ top?: string;
53
+ bottom?: string;
54
+ left?: string;
55
+ right?: string;
56
+ zIndex?: number;
57
+ }
58
+
59
+ export interface FormProviderPropsGenerated {
60
+ child: string;
61
+ attributes: {
62
+ styles?: FormProviderStyleGenerated;
63
+ scrollable?: boolean;
64
+ testID?: string;
65
+ defaultValues?: string;
66
+ validationErrorMessageKey?: string;
67
+ };
68
+ }
69
+
70
+ export interface FormProviderComponentProps {
71
+ node: NodeData<FormProviderPropsGenerated['attributes']>;
72
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "pattern": {
4
+ "type": "FormProvider",
5
+ "title": "FormProvider",
6
+ "description": "Form context provider using react-hook-form. Children (FormCheckbox, FormSubmitButton) use this context for validation and submit.",
7
+ "children": "node",
8
+ "extends": "View",
9
+ "attributes": {
10
+ "defaultValues": "string",
11
+ "validationErrorMessageKey": "string"
12
+ }
13
+ },
14
+ "meta": {
15
+ "desiredParent": ["root", "all"],
16
+ "label": "Form Provider",
17
+ "description": "Wraps form fields and submit button; provides validation state and handleSubmit.",
18
+ "attributes": {
19
+ "defaultValues": {
20
+ "label": "Default values",
21
+ "description": "Optional JSON string of field names to initial values (e.g. {\"termsAccepted\": false}).",
22
+ "category": "other",
23
+ "sort": 1
24
+ },
25
+ "validationErrorMessageKey": {
26
+ "label": "Validation error message key",
27
+ "description": "Localization key shown when submit is attempted with invalid form (e.g. view.terms.error.must_accept).",
28
+ "category": "other",
29
+ "sort": 2
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,52 @@
1
+ import React, { useContext } from 'react';
2
+ import type { FormSubmitButtonComponentProps } from './FormSubmitButtonProps.generated';
3
+ import { formContext } from '../FormProvider/FormProvider';
4
+ import { SystemButton } from '../SystemButton/SystemButton';
5
+ import useNode from '../useNode';
6
+ import { useLogRender } from '../../utils/useLogRender';
7
+ import { useMockOSContext, useMockPermission } from '../../mockOS';
8
+ import { usePlacementButtonEvents } from '../SystemButton/usePlacementButtonEvents';
9
+ import { useGlobalNavigation } from '../GlobalProvider/useGlobalNavigation';
10
+ import { useGlobalContext } from '../GlobalProvider/GlobalContext';
11
+
12
+ export function FormSubmitButton({ node }: FormSubmitButtonComponentProps) {
13
+ useLogRender('FormSubmitButton');
14
+ node = useNode(node);
15
+ const ctx = useContext(formContext);
16
+ const context = useMockOSContext();
17
+ const mockPermissionManager = useMockPermission(context);
18
+ const globalNavigate = useGlobalNavigation();
19
+ const globalCtx = useGlobalContext();
20
+ const attrs = node.attributes;
21
+
22
+ const runPlacementEvents = usePlacementButtonEvents(attrs?.events, {
23
+ context,
24
+ requestPermission: (permission) =>
25
+ mockPermissionManager.requestPermission(permission),
26
+ globalNavigate,
27
+ setCondition: globalCtx?.setCondition,
28
+ });
29
+
30
+ const validationRequired =
31
+ (attrs as { validationRequired?: boolean })?.validationRequired === true;
32
+ const disableIfUnvalidated =
33
+ (attrs as { disableIfUnvalidated?: boolean })?.disableIfUnvalidated ===
34
+ true;
35
+ const disabled =
36
+ validationRequired && disableIfUnvalidated && ctx !== null && !ctx.isValid;
37
+
38
+ const handleClick =
39
+ ctx !== null
40
+ ? ctx.handleSubmit(() => {
41
+ runPlacementEvents();
42
+ })
43
+ : runPlacementEvents;
44
+
45
+ return (
46
+ <SystemButton
47
+ node={node as Parameters<typeof SystemButton>[0]['node']}
48
+ onClick={ctx !== null ? handleClick : undefined}
49
+ disabled={ctx !== null ? disabled : false}
50
+ />
51
+ );
52
+ }
@@ -0,0 +1,114 @@
1
+ /* AUTO-GENERATED FILE - DO NOT EDIT */
2
+
3
+ import type { NodeData } from '../../types/Node';
4
+
5
+ export type TypeOptionType =
6
+ | 'Permission'
7
+ | 'Navigate'
8
+ | 'Placement'
9
+ | 'SetCondition';
10
+ export type PermissionOptionType =
11
+ | 'notification'
12
+ | 'camera'
13
+ | 'microphone'
14
+ | 'location'
15
+ | 'photos'
16
+ | 'contacts'
17
+ | 'att'
18
+ | 'rating'
19
+ | 'GDPR';
20
+ export type PlacementKeyOptionType =
21
+ | 'terms'
22
+ | 'onboard'
23
+ | 'paywall'
24
+ | 'subscription'
25
+ | 'home';
26
+ export type ConditionKeyOptionType = 'termsAccepted';
27
+ export type FlexDirectionOptionType = 'row' | 'column';
28
+ export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
29
+ export type AlignItemsOptionType =
30
+ | 'flex-start'
31
+ | 'center'
32
+ | 'flex-end'
33
+ | 'stretch'
34
+ | 'baseline';
35
+ export type JustifyContentOptionType =
36
+ | 'flex-start'
37
+ | 'center'
38
+ | 'flex-end'
39
+ | 'space-between'
40
+ | 'space-around'
41
+ | 'space-evenly';
42
+ export type PositionOptionType = 'relative' | 'absolute';
43
+
44
+ export interface EventObjectGenerated {
45
+ type?: TypeOptionType;
46
+ permission?: PermissionOptionType;
47
+ navigate_to?: string;
48
+ targetIndex?: number;
49
+ placementKey?: PlacementKeyOptionType;
50
+ conditionKey?: ConditionKeyOptionType;
51
+ value?: boolean;
52
+ }
53
+
54
+ export interface FormSubmitButtonStyleGenerated {
55
+ color?: string;
56
+ backgroundColor?: string;
57
+ height?: string;
58
+ borderRadius?: string;
59
+ fontSize?: string;
60
+ fontWeight?: string;
61
+ fontFamily?: string;
62
+ textAlign?: string;
63
+ flexDirection?: FlexDirectionOptionType;
64
+ flexWrap?: FlexWrapOptionType;
65
+ alignItems?: AlignItemsOptionType;
66
+ justifyContent?: JustifyContentOptionType;
67
+ gap?: string;
68
+ padding?: string;
69
+ paddingHorizontal?: string;
70
+ paddingVertical?: string;
71
+ paddingTop?: string;
72
+ paddingBottom?: string;
73
+ paddingLeft?: string;
74
+ paddingRight?: string;
75
+ margin?: string;
76
+ marginHorizontal?: string;
77
+ marginVertical?: string;
78
+ marginTop?: string;
79
+ marginBottom?: string;
80
+ marginLeft?: string;
81
+ marginRight?: string;
82
+ width?: string;
83
+ minWidth?: string;
84
+ maxWidth?: string;
85
+ minHeight?: string;
86
+ maxHeight?: string;
87
+ flex?: number;
88
+ position?: PositionOptionType;
89
+ top?: string;
90
+ bottom?: string;
91
+ left?: string;
92
+ right?: string;
93
+ zIndex?: number;
94
+ }
95
+
96
+ export interface FormSubmitButtonPropsGenerated {
97
+ child: string;
98
+ attributes: {
99
+ styles?: FormSubmitButtonStyleGenerated;
100
+ labelKey?: string;
101
+ events?: EventObjectGenerated[];
102
+ testID?: string;
103
+ adjustsFontSizeToFit?: boolean;
104
+ numberOfLines?: number;
105
+ translateCounter?: number;
106
+ scrollable?: boolean;
107
+ disableIfUnvalidated?: boolean;
108
+ validationRequired?: boolean;
109
+ };
110
+ }
111
+
112
+ export interface FormSubmitButtonComponentProps {
113
+ node: NodeData<FormSubmitButtonPropsGenerated['attributes']>;
114
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "pattern": {
4
+ "type": "FormSubmitButton",
5
+ "title": "FormSubmitButton",
6
+ "description": "Submit button bound to FormProvider; can disable when form is invalid.",
7
+ "children": "never",
8
+ "extends": "SystemButton",
9
+ "attributes": {
10
+ "disableIfUnvalidated": "boolean",
11
+ "validationRequired": "boolean"
12
+ }
13
+ },
14
+ "meta": {
15
+ "desiredParent": [">FormProvider"],
16
+ "label": "Form Submit Button",
17
+ "description": "Submit button that runs form validation; when validationRequired and disableIfUnvalidated, disabled until form is valid.",
18
+ "attributes": {
19
+ "disableIfUnvalidated": {
20
+ "label": "Disable if unvalidated",
21
+ "description": "When true (and validationRequired is true), button is disabled until form is valid.",
22
+ "category": "other",
23
+ "sort": 4
24
+ },
25
+ "validationRequired": {
26
+ "label": "Validation required",
27
+ "description": "When true, button uses form validation state (e.g. for disableIfUnvalidated).",
28
+ "category": "other",
29
+ "sort": 5
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,48 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { NodeData } from '../../types/Node';
3
+
4
+ /** Supported transition animations when navigating to this page (platform-style names).
5
+ * rn stack transition animations
6
+ */
7
+ export const GLOBAL_PAGE_ANIMATIONS = [
8
+ 'default',
9
+ 'fade',
10
+ 'fade_from_bottom',
11
+ 'fade_from_right',
12
+ 'reveal_from_bottom',
13
+ 'scale_from_center',
14
+ 'slide_from_right',
15
+ 'slide_from_left',
16
+ 'slide_from_bottom',
17
+ 'none',
18
+ ] as const;
19
+
20
+ export type GlobalPageAnimationType = (typeof GLOBAL_PAGE_ANIMATIONS)[number];
21
+
22
+ export interface GlobalPage {
23
+ key: string;
24
+ node: NodeData;
25
+ /** Condition key: this page is skipped when conditions[skipIf] === true */
26
+ skipIf?: string;
27
+ /** Optional transition animation when this page is shown (push/enter). */
28
+ animation?: string;
29
+ index: number;
30
+ }
31
+
32
+ export interface GlobalContextValue {
33
+ currentPageKey: string;
34
+ pages: GlobalPage[];
35
+ pageStack: string[];
36
+ navigate: (key: string) => void;
37
+ goNext: () => void;
38
+ goBack: () => boolean;
39
+ /** Runtime boolean conditions (e.g. termsAccepted) */
40
+ conditions: Record<string, boolean>;
41
+ setCondition: (key: string, value: boolean) => void;
42
+ }
43
+
44
+ export const GlobalContext = createContext<GlobalContextValue | null>(null);
45
+
46
+ export function useGlobalContext(): GlobalContextValue | null {
47
+ return useContext(GlobalContext);
48
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import type { GlobalProviderComponentProps } from './GlobalProviderProps.generated';
3
+ import { GlobalContext } from './GlobalContext';
4
+ import { useGlobalProviderLogic } from './useGlobalProviderLogic';
5
+ import RenderNode from '../RenderNode.generated';
6
+ import { useLogRender } from '../../utils/useLogRender';
7
+
8
+ function GlobalProvider({ node }: GlobalProviderComponentProps) {
9
+ useLogRender('GlobalProvider');
10
+
11
+ const { attributeName, attributeKey, attrs, activePage, contextValue } =
12
+ useGlobalProviderLogic({ node });
13
+
14
+ const animationClass = (() => {
15
+ const a = activePage?.animation;
16
+ if (a === 'default') return 'global-provider-page--default';
17
+ if (!a || a === 'none') return ''; // No modifier: no animation at all
18
+ return `global-provider-page--${a}`;
19
+ })();
20
+
21
+ return (
22
+ <GlobalContext.Provider value={contextValue}>
23
+ <div
24
+ attribute-name={attributeName}
25
+ attribute-key={attributeKey}
26
+ {...(attrs?.testID ? { 'data-testid': attrs.testID } : {})}
27
+ style={{
28
+ flex: 1,
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ height: '100%',
32
+ }}
33
+ >
34
+ {activePage ? (
35
+ <div
36
+ key={activePage.key}
37
+ className={
38
+ animationClass
39
+ ? `global-provider-page ${animationClass}`
40
+ : 'global-provider-page'
41
+ }
42
+ >
43
+ <RenderNode node={activePage.node} />
44
+ </div>
45
+ ) : null}
46
+ </div>
47
+ </GlobalContext.Provider>
48
+ );
49
+ }
50
+
51
+ export default React.memo(GlobalProvider);
@@ -0,0 +1,78 @@
1
+ /* AUTO-GENERATED FILE - DO NOT EDIT */
2
+
3
+ import type { NodeData } from '../../types/Node';
4
+
5
+ export type FlexDirectionOptionType = 'row' | 'column';
6
+ export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
7
+ export type AlignItemsOptionType =
8
+ | 'flex-start'
9
+ | 'center'
10
+ | 'flex-end'
11
+ | 'stretch'
12
+ | 'baseline';
13
+ export type JustifyContentOptionType =
14
+ | 'flex-start'
15
+ | 'center'
16
+ | 'flex-end'
17
+ | 'space-between'
18
+ | 'space-around'
19
+ | 'space-evenly';
20
+ export type PositionOptionType = 'relative' | 'absolute';
21
+
22
+ export interface SkipConditionEntryGenerated {
23
+ pageKey?: string;
24
+ conditionKey?: string;
25
+ }
26
+
27
+ export interface GlobalProviderStyleGenerated {
28
+ flexDirection?: FlexDirectionOptionType;
29
+ flexWrap?: FlexWrapOptionType;
30
+ alignItems?: AlignItemsOptionType;
31
+ justifyContent?: JustifyContentOptionType;
32
+ gap?: string;
33
+ padding?: string;
34
+ paddingHorizontal?: string;
35
+ paddingVertical?: string;
36
+ paddingTop?: string;
37
+ paddingBottom?: string;
38
+ paddingLeft?: string;
39
+ paddingRight?: string;
40
+ margin?: string;
41
+ marginHorizontal?: string;
42
+ marginVertical?: string;
43
+ marginTop?: string;
44
+ marginBottom?: string;
45
+ marginLeft?: string;
46
+ marginRight?: string;
47
+ backgroundColor?: string;
48
+ borderRadius?: string;
49
+ width?: string;
50
+ minWidth?: string;
51
+ maxWidth?: string;
52
+ height?: string;
53
+ minHeight?: string;
54
+ maxHeight?: string;
55
+ flex?: number;
56
+ position?: PositionOptionType;
57
+ top?: string;
58
+ bottom?: string;
59
+ left?: string;
60
+ right?: string;
61
+ zIndex?: number;
62
+ }
63
+
64
+ export interface GlobalProviderPropsGenerated {
65
+ child: string;
66
+ attributes: {
67
+ styles?: GlobalProviderStyleGenerated;
68
+ scrollable?: boolean;
69
+ testID?: string;
70
+ initialPage?: string;
71
+ persistProgress?: boolean;
72
+ skipConditions?: SkipConditionEntryGenerated[];
73
+ };
74
+ }
75
+
76
+ export interface GlobalProviderComponentProps {
77
+ node: NodeData<GlobalProviderPropsGenerated['attributes']>;
78
+ }
@@ -0,0 +1,204 @@
1
+ import type { NodeData } from '../../types/Node';
2
+ import type { GlobalPage } from './GlobalContext';
3
+
4
+ /** Maps node type to a canonical page key when no explicit pageKey attribute is provided. */
5
+ const TYPE_KEY_MAP: Record<string, string> = {
6
+ TermsProvider: 'terms',
7
+ OnboardProvider: 'onboard',
8
+ PaywallProvider: 'paywall',
9
+ FormProvider: 'form',
10
+ CarouselProvider: 'carousel',
11
+ };
12
+
13
+ /**
14
+ * Derives a stable and unique page key for a child node.
15
+ * Priority: node.key → TYPE_KEY_MAP → "${type}-${index}"
16
+ */
17
+ export function derivePageKey(
18
+ node: NodeData,
19
+ index: number,
20
+ existingKeys: Set<string>,
21
+ ): string {
22
+ let baseKey = '';
23
+ if (typeof node.key === 'string' && node.key.trim()) {
24
+ baseKey = node.key.trim();
25
+ } else {
26
+ baseKey = TYPE_KEY_MAP[node.type] || node.type?.toLowerCase?.() || 'page';
27
+ }
28
+
29
+ let finalKey = baseKey;
30
+ let counter = 1;
31
+
32
+ // Ensure uniqueness among siblings
33
+ while (existingKeys.has(finalKey)) {
34
+ finalKey = `${baseKey}-${index + counter}`;
35
+ counter++;
36
+ }
37
+
38
+ existingKeys.add(finalKey);
39
+ return finalKey;
40
+ }
41
+
42
+ /** Runtime shape of a SkipConditionEntry (mirrors the types block in pattern.json). */
43
+ export interface SkipConditionEntry {
44
+ pageKey: string;
45
+ conditionKey: string;
46
+ }
47
+
48
+ /**
49
+ * Converts SkipConditionEntry[] (from pattern schema) to a lookup map for fast access.
50
+ */
51
+ export function normalizeSkipConditions(raw: unknown): Record<string, string> {
52
+ if (!Array.isArray(raw)) return {};
53
+ const result: Record<string, string> = {};
54
+ for (const entry of raw) {
55
+ if (
56
+ entry &&
57
+ typeof entry === 'object' &&
58
+ typeof (entry as SkipConditionEntry).pageKey === 'string' &&
59
+ typeof (entry as SkipConditionEntry).conditionKey === 'string'
60
+ ) {
61
+ result[(entry as SkipConditionEntry).pageKey] = (
62
+ entry as SkipConditionEntry
63
+ ).conditionKey;
64
+ }
65
+ }
66
+ return result;
67
+ }
68
+
69
+ /**
70
+ * Builds the page list for GlobalProvider.
71
+ */
72
+ export function buildPages(
73
+ children: NodeData[],
74
+ skipConditions: Record<string, string> = {},
75
+ ): GlobalPage[] {
76
+ const existingKeys = new Set<string>();
77
+ return children.map((node, index) => {
78
+ const key = derivePageKey(node, index, existingKeys);
79
+ const skipIf = skipConditions[key] ?? undefined;
80
+ const animation =
81
+ typeof (node.attributes as Record<string, unknown>)?.animation ===
82
+ 'string'
83
+ ? (node.attributes as Record<string, string>).animation
84
+ : undefined;
85
+ return {
86
+ key,
87
+ node,
88
+ skipIf,
89
+ animation,
90
+ index,
91
+ };
92
+ });
93
+ }
94
+
95
+ /** Returns the first non-skipped page key, starting from the requested key. */
96
+ export function resolveEffectivePage(
97
+ targetKey: string,
98
+ pages: GlobalPage[],
99
+ conditions: Record<string, boolean>,
100
+ ): string | null {
101
+ const targetIdx = pages.findIndex((p) => p.key === targetKey);
102
+ if (targetIdx === -1) {
103
+ // If target not found, start from the first non-skipped page
104
+ return resolveFirstNonSkippedPage(pages, conditions);
105
+ }
106
+
107
+ // Walk forward from target, skipping pages whose condition is met
108
+ for (let i = targetIdx; i < pages.length; i++) {
109
+ const page = pages[i];
110
+ if (!page) break;
111
+ if (page.skipIf && conditions[page.skipIf] === true) {
112
+ continue;
113
+ }
114
+ return page.key;
115
+ }
116
+
117
+ // If all remaining pages are skipped, return null instead of falling back to the last page.
118
+ // This allows the caller to handle the "no available pages" state explicitly.
119
+ return null;
120
+ }
121
+
122
+ function resolveFirstNonSkippedPage(
123
+ pages: GlobalPage[],
124
+ conditions: Record<string, boolean>,
125
+ ): string | null {
126
+ for (const page of pages) {
127
+ if (!(page.skipIf && conditions[page.skipIf] === true)) {
128
+ return page.key;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+
134
+ const STORAGE_KEY_PREFIX = 'global-provider-progress';
135
+
136
+ /**
137
+ * Internal storage abstraction. In a real RN environment, this would be swapped
138
+ * for AsyncStorage. For now, it defaults to localStorage with safe checks.
139
+ */
140
+ const storage = {
141
+ getItem: (key: string) => {
142
+ try {
143
+ return typeof localStorage !== 'undefined'
144
+ ? localStorage.getItem(key)
145
+ : null;
146
+ } catch {
147
+ return null;
148
+ }
149
+ },
150
+ setItem: (key: string, value: string) => {
151
+ try {
152
+ if (typeof localStorage !== 'undefined') localStorage.setItem(key, value);
153
+ } catch {
154
+ // ignore
155
+ }
156
+ },
157
+ removeItem: (key: string) => {
158
+ try {
159
+ if (typeof localStorage !== 'undefined') localStorage.removeItem(key);
160
+ } catch {
161
+ // ignore
162
+ }
163
+ },
164
+ };
165
+
166
+ export function persistProgress(
167
+ storageKey: string,
168
+ currentPageKey: string,
169
+ conditions: Record<string, boolean>,
170
+ ): void {
171
+ storage.setItem(
172
+ `${STORAGE_KEY_PREFIX}-${storageKey}`,
173
+ JSON.stringify({ currentPageKey, conditions }),
174
+ );
175
+ }
176
+
177
+ export function loadProgress(storageKey: string): {
178
+ currentPageKey: string;
179
+ conditions: Record<string, boolean>;
180
+ } | null {
181
+ const raw = storage.getItem(`${STORAGE_KEY_PREFIX}-${storageKey}`);
182
+ if (!raw) return null;
183
+ try {
184
+ const parsed = JSON.parse(raw) as unknown;
185
+ if (
186
+ parsed &&
187
+ typeof parsed === 'object' &&
188
+ !Array.isArray(parsed) &&
189
+ typeof (parsed as Record<string, unknown>).currentPageKey === 'string'
190
+ ) {
191
+ return parsed as {
192
+ currentPageKey: string;
193
+ conditions: Record<string, boolean>;
194
+ };
195
+ }
196
+ } catch {
197
+ // parse error
198
+ }
199
+ return null;
200
+ }
201
+
202
+ export function clearProgress(storageKey: string): void {
203
+ storage.removeItem(`${STORAGE_KEY_PREFIX}-${storageKey}`);
204
+ }