@developer_tribe/react-builder 1.2.42 → 1.2.44-test.1
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.
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +1 -1
- package/dist/build-components/Checkbox/Checkbox.d.ts +6 -0
- package/dist/build-components/Checkbox/CheckboxProps.generated.d.ts +67 -0
- package/dist/build-components/CountDown/CountDownProps.generated.d.ts +1 -1
- package/dist/build-components/FormCheckbox/FormCheckbox.d.ts +3 -0
- package/dist/build-components/FormCheckbox/FormCheckboxProps.generated.d.ts +69 -0
- package/dist/build-components/FormErrorText/FormErrorText.d.ts +3 -0
- package/dist/build-components/FormErrorText/FormErrorTextProps.generated.d.ts +61 -0
- package/dist/build-components/FormProvider/FormProvider.d.ts +11 -0
- package/dist/build-components/FormProvider/FormProviderProps.generated.d.ts +55 -0
- package/dist/build-components/FormSubmitButton/FormSubmitButton.d.ts +2 -0
- package/dist/build-components/FormSubmitButton/FormSubmitButtonProps.generated.d.ts +73 -0
- package/dist/build-components/GlobalProvider/GlobalContext.d.ts +28 -0
- package/dist/build-components/GlobalProvider/GlobalProvider.d.ts +5 -0
- package/dist/build-components/GlobalProvider/GlobalProviderProps.generated.d.ts +60 -0
- package/dist/build-components/GlobalProvider/globalProviderUtils.d.ts +37 -0
- package/dist/build-components/GlobalProvider/useGlobalNavigation.d.ts +19 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +17 -10
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallFooter/PaywallFooterProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +2 -0
- package/dist/build-components/PriceTag/PriceTagProps.generated.d.ts +1 -1
- package/dist/build-components/Pricing/PricingProps.generated.d.ts +1 -1
- package/dist/build-components/Promo/PromoProps.generated.d.ts +1 -1
- package/dist/build-components/SystemButton/SystemButton.d.ts +7 -0
- package/dist/build-components/SystemButton/SystemButtonProps.generated.d.ts +71 -0
- package/dist/build-components/SystemButton/usePlacementButtonEvents.d.ts +28 -0
- package/dist/build-components/TermsProvider/TermsProvider.d.ts +5 -0
- package/dist/build-components/TermsProvider/TermsProviderProps.generated.d.ts +55 -0
- package/dist/build-components/Text/TextProps.generated.d.ts +1 -1
- package/dist/build-components/WebView/WebView.d.ts +2 -0
- package/dist/build-components/WebView/WebViewProps.generated.d.ts +59 -0
- package/dist/build-components/index.d.ts +10 -1
- package/dist/build-components/patterns.generated.d.ts +5507 -1543
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +5 -5
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.d.ts +1 -0
- package/dist/index.web.esm.js +4 -4
- package/dist/index.web.esm.js.map +1 -1
- package/dist/mockOS/context/MockOSContextBase.d.ts +3 -1
- package/dist/styles.css +1 -1
- package/dist/types/PreviewConfig.d.ts +1 -1
- package/package.json +2 -1
- package/src/RenderPage.tsx +4 -1
- package/src/assets/meta.json +1 -1
- package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
- package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
- package/src/assets/samples/getSamples.ts +7 -0
- package/src/assets/samples/global-onboard-flow.json +729 -0
- package/src/assets/samples/paywall-1.json +2 -2
- package/src/assets/samples/terms-and-privacy-no-form.json +108 -0
- package/src/assets/samples/terms-and-privacy.json +130 -0
- package/src/attributes-editor/FallbackLocalizationField.tsx +725 -250
- package/src/build-components/BIcon/BIconProps.generated.ts +1 -1
- package/src/build-components/Checkbox/Checkbox.tsx +165 -0
- package/src/build-components/Checkbox/CheckboxProps.generated.ts +84 -0
- package/src/build-components/Checkbox/pattern.json +83 -0
- package/src/build-components/CountDown/CountDownProps.generated.ts +1 -1
- package/src/build-components/FormCheckbox/FormCheckbox.tsx +106 -0
- package/src/build-components/FormCheckbox/FormCheckboxProps.generated.ts +86 -0
- package/src/build-components/FormCheckbox/pattern.json +39 -0
- package/src/build-components/FormErrorText/FormErrorText.tsx +34 -0
- package/src/build-components/FormErrorText/FormErrorTextProps.generated.ts +78 -0
- package/src/build-components/FormErrorText/pattern.json +21 -0
- package/src/build-components/FormProvider/FormProvider.tsx +131 -0
- package/src/build-components/FormProvider/FormProviderProps.generated.ts +72 -0
- package/src/build-components/FormProvider/pattern.json +33 -0
- package/src/build-components/FormSubmitButton/FormSubmitButton.tsx +49 -0
- package/src/build-components/FormSubmitButton/FormSubmitButtonProps.generated.ts +91 -0
- package/src/build-components/FormSubmitButton/pattern.json +33 -0
- package/src/build-components/GlobalProvider/GlobalContext.ts +48 -0
- package/src/build-components/GlobalProvider/GlobalProvider.tsx +191 -0
- package/src/build-components/GlobalProvider/GlobalProviderProps.generated.ts +78 -0
- package/src/build-components/GlobalProvider/globalProviderUtils.ts +163 -0
- package/src/build-components/GlobalProvider/pattern.json +55 -0
- package/src/build-components/GlobalProvider/useGlobalNavigation.ts +70 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +41 -36
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +17 -10
- package/src/build-components/OnboardButton/pattern.json +5 -4
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +1 -1
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +12 -0
- package/src/build-components/OnboardProvider/pattern.json +9 -1
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +1 -1
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +1 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +1 -1
- package/src/build-components/PaywallFooter/PaywallFooterProps.generated.ts +1 -1
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +12 -0
- package/src/build-components/PaywallProvider/pattern.json +9 -1
- package/src/build-components/PriceTag/PriceTagProps.generated.ts +1 -1
- package/src/build-components/Pricing/PricingProps.generated.ts +1 -1
- package/src/build-components/Promo/PromoProps.generated.ts +1 -1
- package/src/build-components/RenderNode.generated.tsx +46 -1
- package/src/build-components/SystemButton/SystemButton.tsx +71 -0
- package/src/build-components/SystemButton/SystemButtonProps.generated.ts +89 -0
- package/src/build-components/SystemButton/pattern.json +61 -0
- package/src/build-components/SystemButton/usePlacementButtonEvents.ts +101 -0
- package/src/build-components/TermsProvider/TermsProvider.tsx +45 -0
- package/src/build-components/TermsProvider/TermsProviderProps.generated.ts +82 -0
- package/src/build-components/TermsProvider/pattern.json +35 -0
- package/src/build-components/Text/Text.tsx +5 -4
- package/src/build-components/Text/TextProps.generated.ts +1 -1
- package/src/build-components/Text/pattern.json +2 -1
- package/src/build-components/WebView/WebView.tsx +149 -0
- package/src/build-components/WebView/WebViewProps.generated.ts +76 -0
- package/src/build-components/WebView/pattern.json +71 -0
- package/src/build-components/index.ts +45 -0
- package/src/build-components/patterns.generated.ts +5712 -1559
- package/src/hooks/useSafeAreaViewStyle.ts +1 -11
- package/src/index.web.ts +3 -0
- package/src/mockOS/components/MockOSRouter.tsx +21 -0
- package/src/mockOS/context/MockOSContext.tsx +7 -0
- package/src/mockOS/context/MockOSContextBase.ts +4 -0
- package/src/styles/components/_checkbox.scss +19 -0
- package/src/styles/components/_global-provider.scss +131 -0
- package/src/styles/components/_webview.scss +52 -0
- package/src/styles/index.scss +4 -0
- package/src/types/PreviewConfig.ts +19 -0
- package/src/utils/analyseNodeByPatterns.ts +5 -2
- package/src/utils/extractViewStyle/extractViewStyle.ts +0 -1
- package/src/utils/projectColors.ts +4 -0
- package/src/utils/useMergedStyle.ts +1 -1
- package/src/.DS_Store +0 -0
- package/src/assets/.DS_Store +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"pattern": {
|
|
4
|
+
"type": "FormErrorText",
|
|
5
|
+
"title": "FormErrorText",
|
|
6
|
+
"description": "Displays FormProvider global submit/async error as text. Only valid as child of FormProvider.",
|
|
7
|
+
"children": "never",
|
|
8
|
+
"extends": "Text"
|
|
9
|
+
},
|
|
10
|
+
"defaults": {
|
|
11
|
+
"styles": {
|
|
12
|
+
"color": "THEME_COLORS.FORM_ERROR_TEXT_COLOR",
|
|
13
|
+
"fontSize": "14@fs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"meta": {
|
|
17
|
+
"desiredParent": [">FormProvider"],
|
|
18
|
+
"label": "Form Error Text",
|
|
19
|
+
"description": "Shows form global error (e.g. submit failure). Place inside FormProvider where the error should appear."
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { FormProvider as RHFFormProvider, useForm } from 'react-hook-form';
|
|
3
|
+
import type { UseFormReturn } from 'react-hook-form';
|
|
4
|
+
import type { FormProviderComponentProps } from './FormProviderProps.generated';
|
|
5
|
+
import RenderNode from '../RenderNode.generated';
|
|
6
|
+
import useNode from '../useNode';
|
|
7
|
+
import { useLogRender } from '../../utils/useLogRender';
|
|
8
|
+
import { useExtractViewStyle } from '../../attribute-analyser/style/web/useExtractViewStyle';
|
|
9
|
+
|
|
10
|
+
export type FormContextValue = {
|
|
11
|
+
handleSubmit: (
|
|
12
|
+
callback: (data: Record<string, unknown>) => void | Promise<unknown>,
|
|
13
|
+
) => (e?: React.BaseSyntheticEvent) => void;
|
|
14
|
+
isValid: boolean;
|
|
15
|
+
globalError: string | null;
|
|
16
|
+
control: UseFormReturn<Record<string, unknown>>['control'];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const formContext = createContext<FormContextValue | null>(null);
|
|
20
|
+
|
|
21
|
+
function parseDefaultValues(raw: string | undefined): Record<string, unknown> {
|
|
22
|
+
if (raw == null || typeof raw !== 'string' || raw.trim() === '') {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
27
|
+
return typeof parsed === 'object' &&
|
|
28
|
+
parsed !== null &&
|
|
29
|
+
!Array.isArray(parsed)
|
|
30
|
+
? (parsed as Record<string, unknown>)
|
|
31
|
+
: {};
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.warn(
|
|
34
|
+
'[FormProvider] parseDefaultValues: invalid defaultValues JSON',
|
|
35
|
+
raw,
|
|
36
|
+
err,
|
|
37
|
+
);
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function FormProviderInner({ node }: FormProviderComponentProps) {
|
|
43
|
+
useLogRender('FormProvider');
|
|
44
|
+
node = useNode(node);
|
|
45
|
+
const attrs = node.attributes;
|
|
46
|
+
const defaultValues = useMemo(
|
|
47
|
+
() => parseDefaultValues(attrs?.defaultValues as string | undefined),
|
|
48
|
+
[attrs?.defaultValues],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const form = useForm<Record<string, unknown>>({
|
|
52
|
+
defaultValues,
|
|
53
|
+
mode: 'onChange',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const [globalError, setGlobalError] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
const validationErrorKey =
|
|
59
|
+
(attrs?.validationErrorMessageKey as string | undefined) ??
|
|
60
|
+
'form.error.validation_required';
|
|
61
|
+
|
|
62
|
+
const handleSubmit = useCallback(
|
|
63
|
+
(callback: (data: Record<string, unknown>) => void | Promise<unknown>) => {
|
|
64
|
+
const onValid = (data: Record<string, unknown>) => {
|
|
65
|
+
setGlobalError(null);
|
|
66
|
+
try {
|
|
67
|
+
const result = callback(data);
|
|
68
|
+
const promise =
|
|
69
|
+
result !== undefined &&
|
|
70
|
+
result !== null &&
|
|
71
|
+
typeof (result as Promise<unknown>).then === 'function'
|
|
72
|
+
? (result as Promise<unknown>)
|
|
73
|
+
: null;
|
|
74
|
+
if (promise) {
|
|
75
|
+
promise.catch((e: unknown) => {
|
|
76
|
+
setGlobalError(e instanceof Error ? e.message : String(e));
|
|
77
|
+
throw e;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
setGlobalError(e instanceof Error ? e.message : String(e));
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const onInvalid = () => {
|
|
86
|
+
setGlobalError(validationErrorKey);
|
|
87
|
+
};
|
|
88
|
+
return form.handleSubmit(onValid, onInvalid);
|
|
89
|
+
},
|
|
90
|
+
[form, validationErrorKey],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const contextValue = useMemo<FormContextValue>(
|
|
94
|
+
() => ({
|
|
95
|
+
handleSubmit,
|
|
96
|
+
isValid: form.formState.isValid,
|
|
97
|
+
globalError,
|
|
98
|
+
control: form.control,
|
|
99
|
+
}),
|
|
100
|
+
[handleSubmit, form.formState.isValid, form.control, globalError],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const viewStyle = useExtractViewStyle(node);
|
|
104
|
+
|
|
105
|
+
const onSubmit = (e: React.FormEvent) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<formContext.Provider value={contextValue}>
|
|
111
|
+
<RHFFormProvider {...form}>
|
|
112
|
+
<form
|
|
113
|
+
onSubmit={onSubmit}
|
|
114
|
+
style={viewStyle}
|
|
115
|
+
attribute-name={node.sourceType ?? node.type ?? 'FormProvider'}
|
|
116
|
+
attribute-key={node.key}
|
|
117
|
+
>
|
|
118
|
+
{node.children ? (
|
|
119
|
+
<RenderNode
|
|
120
|
+
node={node.children as Parameters<typeof RenderNode>[0]['node']}
|
|
121
|
+
/>
|
|
122
|
+
) : null}
|
|
123
|
+
</form>
|
|
124
|
+
</RHFFormProvider>
|
|
125
|
+
</formContext.Provider>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function FormProvider(props: FormProviderComponentProps) {
|
|
130
|
+
return <FormProviderInner node={props.node} />;
|
|
131
|
+
}
|
|
@@ -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,49 @@
|
|
|
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
|
+
|
|
11
|
+
export function FormSubmitButton({ node }: FormSubmitButtonComponentProps) {
|
|
12
|
+
useLogRender('FormSubmitButton');
|
|
13
|
+
node = useNode(node);
|
|
14
|
+
const ctx = useContext(formContext);
|
|
15
|
+
const context = useMockOSContext();
|
|
16
|
+
const mockPermissionManager = useMockPermission(context);
|
|
17
|
+
const globalNavigate = useGlobalNavigation();
|
|
18
|
+
const attrs = node.attributes;
|
|
19
|
+
|
|
20
|
+
const runPlacementEvents = usePlacementButtonEvents(attrs?.events, {
|
|
21
|
+
context,
|
|
22
|
+
requestPermission: (permission) =>
|
|
23
|
+
mockPermissionManager.requestPermission(permission),
|
|
24
|
+
globalNavigate,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const validationRequired =
|
|
28
|
+
(attrs as { validationRequired?: boolean })?.validationRequired === true;
|
|
29
|
+
const disableIfUnvalidated =
|
|
30
|
+
(attrs as { disableIfUnvalidated?: boolean })?.disableIfUnvalidated ===
|
|
31
|
+
true;
|
|
32
|
+
const disabled =
|
|
33
|
+
validationRequired && disableIfUnvalidated && ctx !== null && !ctx.isValid;
|
|
34
|
+
|
|
35
|
+
const handleClick =
|
|
36
|
+
ctx !== null
|
|
37
|
+
? ctx.handleSubmit(() => {
|
|
38
|
+
runPlacementEvents();
|
|
39
|
+
})
|
|
40
|
+
: runPlacementEvents;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<SystemButton
|
|
44
|
+
node={node as Parameters<typeof SystemButton>[0]['node']}
|
|
45
|
+
onClick={ctx !== null ? handleClick : undefined}
|
|
46
|
+
disabled={ctx !== null ? disabled : false}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* AUTO-GENERATED FILE - DO NOT EDIT */
|
|
2
|
+
|
|
3
|
+
import type { NodeData } from '../../types/Node';
|
|
4
|
+
|
|
5
|
+
export type TypeOptionType = 'Permission' | 'Navigate' | 'Placement';
|
|
6
|
+
export type FlexDirectionOptionType = 'row' | 'column';
|
|
7
|
+
export type FlexWrapOptionType = 'nowrap' | 'wrap' | 'wrap-reverse';
|
|
8
|
+
export type AlignItemsOptionType =
|
|
9
|
+
| 'flex-start'
|
|
10
|
+
| 'center'
|
|
11
|
+
| 'flex-end'
|
|
12
|
+
| 'stretch'
|
|
13
|
+
| 'baseline';
|
|
14
|
+
export type JustifyContentOptionType =
|
|
15
|
+
| 'flex-start'
|
|
16
|
+
| 'center'
|
|
17
|
+
| 'flex-end'
|
|
18
|
+
| 'space-between'
|
|
19
|
+
| 'space-around'
|
|
20
|
+
| 'space-evenly';
|
|
21
|
+
export type PositionOptionType = 'relative' | 'absolute';
|
|
22
|
+
|
|
23
|
+
export interface EventObjectGenerated {
|
|
24
|
+
type?: TypeOptionType;
|
|
25
|
+
permission?: string;
|
|
26
|
+
navigate_to?: string;
|
|
27
|
+
targetIndex?: number;
|
|
28
|
+
placementKey?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FormSubmitButtonStyleGenerated {
|
|
32
|
+
color?: string;
|
|
33
|
+
backgroundColor?: string;
|
|
34
|
+
height?: string;
|
|
35
|
+
borderRadius?: string;
|
|
36
|
+
fontSize?: string;
|
|
37
|
+
fontWeight?: string;
|
|
38
|
+
fontFamily?: string;
|
|
39
|
+
textAlign?: string;
|
|
40
|
+
flexDirection?: FlexDirectionOptionType;
|
|
41
|
+
flexWrap?: FlexWrapOptionType;
|
|
42
|
+
alignItems?: AlignItemsOptionType;
|
|
43
|
+
justifyContent?: JustifyContentOptionType;
|
|
44
|
+
gap?: string;
|
|
45
|
+
padding?: string;
|
|
46
|
+
paddingHorizontal?: string;
|
|
47
|
+
paddingVertical?: string;
|
|
48
|
+
paddingTop?: string;
|
|
49
|
+
paddingBottom?: string;
|
|
50
|
+
paddingLeft?: string;
|
|
51
|
+
paddingRight?: string;
|
|
52
|
+
margin?: string;
|
|
53
|
+
marginHorizontal?: string;
|
|
54
|
+
marginVertical?: string;
|
|
55
|
+
marginTop?: string;
|
|
56
|
+
marginBottom?: string;
|
|
57
|
+
marginLeft?: string;
|
|
58
|
+
marginRight?: string;
|
|
59
|
+
width?: string;
|
|
60
|
+
minWidth?: string;
|
|
61
|
+
maxWidth?: string;
|
|
62
|
+
minHeight?: string;
|
|
63
|
+
maxHeight?: string;
|
|
64
|
+
flex?: number;
|
|
65
|
+
position?: PositionOptionType;
|
|
66
|
+
top?: string;
|
|
67
|
+
bottom?: string;
|
|
68
|
+
left?: string;
|
|
69
|
+
right?: string;
|
|
70
|
+
zIndex?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface FormSubmitButtonPropsGenerated {
|
|
74
|
+
child: string;
|
|
75
|
+
attributes: {
|
|
76
|
+
styles?: FormSubmitButtonStyleGenerated;
|
|
77
|
+
labelKey?: string;
|
|
78
|
+
events?: EventObjectGenerated[];
|
|
79
|
+
testID?: string;
|
|
80
|
+
adjustsFontSizeToFit?: boolean;
|
|
81
|
+
numberOfLines?: number;
|
|
82
|
+
translateCounter?: number;
|
|
83
|
+
scrollable?: boolean;
|
|
84
|
+
disableIfUnvalidated?: boolean;
|
|
85
|
+
validationRequired?: boolean;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FormSubmitButtonComponentProps {
|
|
90
|
+
node: NodeData<FormSubmitButtonPropsGenerated['attributes']>;
|
|
91
|
+
}
|
|
@@ -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,191 @@
|
|
|
1
|
+
import React, { useCallback, useId, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { GlobalProviderComponentProps } from './GlobalProviderProps.generated';
|
|
3
|
+
import type { GlobalContextValue, GlobalPage } from './GlobalContext';
|
|
4
|
+
import { GlobalContext } from './GlobalContext';
|
|
5
|
+
import {
|
|
6
|
+
buildPages,
|
|
7
|
+
loadProgress,
|
|
8
|
+
normalizeSkipConditions,
|
|
9
|
+
persistProgress,
|
|
10
|
+
resolveEffectivePage,
|
|
11
|
+
} from './globalProviderUtils';
|
|
12
|
+
import RenderNode from '../RenderNode.generated';
|
|
13
|
+
import useNode from '../useNode';
|
|
14
|
+
import { useLogRender } from '../../utils/useLogRender';
|
|
15
|
+
import type { NodeData } from '../../types/Node';
|
|
16
|
+
|
|
17
|
+
function GlobalProvider({ node }: GlobalProviderComponentProps) {
|
|
18
|
+
useLogRender('GlobalProvider');
|
|
19
|
+
node = useNode(node);
|
|
20
|
+
|
|
21
|
+
const generatedId = useId();
|
|
22
|
+
const attributeName = node.sourceType ?? node.type ?? 'GlobalProvider';
|
|
23
|
+
const attributeKey = node.key ?? generatedId;
|
|
24
|
+
const attrs = node.attributes;
|
|
25
|
+
const shouldPersist = attrs?.persistProgress === true;
|
|
26
|
+
|
|
27
|
+
const childNodes = useMemo((): NodeData[] => {
|
|
28
|
+
const raw = node.children;
|
|
29
|
+
if (!raw) return [];
|
|
30
|
+
if (Array.isArray(raw)) return raw as NodeData[];
|
|
31
|
+
return [raw as NodeData];
|
|
32
|
+
}, [node.children]);
|
|
33
|
+
|
|
34
|
+
// skipConditions is stored as SkipConditionEntry[] in the schema (array of
|
|
35
|
+
// {pageKey, conditionKey} objects). Normalize to a lookup map here.
|
|
36
|
+
const skipConditions = useMemo(
|
|
37
|
+
() => normalizeSkipConditions(attrs?.skipConditions),
|
|
38
|
+
[attrs?.skipConditions],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const pages = useMemo(
|
|
42
|
+
() => buildPages(childNodes, skipConditions),
|
|
43
|
+
[childNodes, skipConditions],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const storageKey = attributeKey;
|
|
47
|
+
|
|
48
|
+
const [conditions, setConditions] = useState<Record<string, boolean>>(() => {
|
|
49
|
+
if (shouldPersist) {
|
|
50
|
+
return loadProgress(storageKey)?.conditions ?? {};
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const [pageStack, setPageStack] = useState<string[]>(() => {
|
|
56
|
+
const firstEffective = (() => {
|
|
57
|
+
if (shouldPersist) {
|
|
58
|
+
const saved = loadProgress(storageKey);
|
|
59
|
+
if (saved?.currentPageKey) return saved.currentPageKey;
|
|
60
|
+
}
|
|
61
|
+
const requestedKey =
|
|
62
|
+
typeof attrs?.initialPage === 'string'
|
|
63
|
+
? attrs.initialPage
|
|
64
|
+
: (pages[0]?.key ?? '');
|
|
65
|
+
return resolveEffectivePage(requestedKey, pages, {});
|
|
66
|
+
})();
|
|
67
|
+
return firstEffective ? [firstEffective] : [];
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const currentPageKey = pageStack[pageStack.length - 1] ?? pages[0]?.key ?? '';
|
|
71
|
+
|
|
72
|
+
// Keep ref of latest conditions for persist calls in callbacks
|
|
73
|
+
const conditionsRef = useRef(conditions);
|
|
74
|
+
conditionsRef.current = conditions;
|
|
75
|
+
|
|
76
|
+
const setCondition = useCallback(
|
|
77
|
+
(key: string, value: boolean) => {
|
|
78
|
+
setConditions((prev) => {
|
|
79
|
+
const next = { ...prev, [key]: value };
|
|
80
|
+
if (shouldPersist) {
|
|
81
|
+
const currentKey = pageStack[pageStack.length - 1] ?? '';
|
|
82
|
+
persistProgress(storageKey, currentKey, next);
|
|
83
|
+
}
|
|
84
|
+
return next;
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
[shouldPersist, storageKey, pageStack],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const navigate = useCallback(
|
|
91
|
+
(key: string) => {
|
|
92
|
+
const effective = resolveEffectivePage(key, pages, conditionsRef.current);
|
|
93
|
+
setPageStack((prev) => {
|
|
94
|
+
const last = prev[prev.length - 1];
|
|
95
|
+
if (last === effective) return prev;
|
|
96
|
+
const next = [...prev, effective];
|
|
97
|
+
if (shouldPersist) {
|
|
98
|
+
persistProgress(storageKey, effective, conditionsRef.current);
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
[pages, shouldPersist, storageKey],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const goNext = useCallback(() => {
|
|
107
|
+
const currentIdx = pages.findIndex((p) => p.key === currentPageKey);
|
|
108
|
+
if (currentIdx === -1 || currentIdx >= pages.length - 1) return;
|
|
109
|
+
const nextPage = pages[currentIdx + 1];
|
|
110
|
+
if (!nextPage) return;
|
|
111
|
+
navigate(nextPage.key);
|
|
112
|
+
}, [pages, currentPageKey, navigate]);
|
|
113
|
+
|
|
114
|
+
const goBack = useCallback((): boolean => {
|
|
115
|
+
if (pageStack.length <= 1) return false;
|
|
116
|
+
setPageStack((prev) => {
|
|
117
|
+
const next = prev.slice(0, -1);
|
|
118
|
+
const prevKey = next[next.length - 1] ?? '';
|
|
119
|
+
if (shouldPersist) {
|
|
120
|
+
persistProgress(storageKey, prevKey, conditionsRef.current);
|
|
121
|
+
}
|
|
122
|
+
return next;
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
}, [pageStack.length, shouldPersist, storageKey]);
|
|
126
|
+
|
|
127
|
+
const contextValue = useMemo<GlobalContextValue>(
|
|
128
|
+
() => ({
|
|
129
|
+
currentPageKey,
|
|
130
|
+
pages,
|
|
131
|
+
pageStack,
|
|
132
|
+
navigate,
|
|
133
|
+
goNext,
|
|
134
|
+
goBack,
|
|
135
|
+
conditions,
|
|
136
|
+
setCondition,
|
|
137
|
+
}),
|
|
138
|
+
[
|
|
139
|
+
currentPageKey,
|
|
140
|
+
pages,
|
|
141
|
+
pageStack,
|
|
142
|
+
navigate,
|
|
143
|
+
goNext,
|
|
144
|
+
goBack,
|
|
145
|
+
conditions,
|
|
146
|
+
setCondition,
|
|
147
|
+
],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const activePage: GlobalPage | undefined = pages.find(
|
|
151
|
+
(p) => p.key === currentPageKey,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const animationClass = (() => {
|
|
155
|
+
const a = activePage?.animation;
|
|
156
|
+
if (a === 'default') return 'global-provider-page--default';
|
|
157
|
+
if (!a || a === 'none') return ''; // No modifier: no animation at all
|
|
158
|
+
return `global-provider-page--${a}`;
|
|
159
|
+
})();
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<GlobalContext.Provider value={contextValue}>
|
|
163
|
+
<div
|
|
164
|
+
attribute-name={attributeName}
|
|
165
|
+
attribute-key={attributeKey}
|
|
166
|
+
{...(attrs?.testID ? { 'data-testid': attrs.testID } : {})}
|
|
167
|
+
style={{
|
|
168
|
+
flex: 1,
|
|
169
|
+
display: 'flex',
|
|
170
|
+
flexDirection: 'column',
|
|
171
|
+
height: '100%',
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
{activePage ? (
|
|
175
|
+
<div
|
|
176
|
+
key={activePage.key}
|
|
177
|
+
className={
|
|
178
|
+
animationClass
|
|
179
|
+
? `global-provider-page ${animationClass}`
|
|
180
|
+
: 'global-provider-page'
|
|
181
|
+
}
|
|
182
|
+
>
|
|
183
|
+
<RenderNode node={activePage.node} />
|
|
184
|
+
</div>
|
|
185
|
+
) : null}
|
|
186
|
+
</div>
|
|
187
|
+
</GlobalContext.Provider>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default React.memo(GlobalProvider);
|