@developer_tribe/react-builder 1.2.21 → 1.2.23
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/attribute-analyser/style/native/useExtractImageStyle.d.ts +2 -2
- package/dist/build-components/Image/ImageProps.generated.d.ts +2 -4
- package/dist/build-components/NavigationBarColor/NavigationBarColor.d.ts +5 -0
- package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +54 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -3
- package/dist/build-components/Separator/Separator.d.ts +5 -0
- package/dist/build-components/Separator/SeparatorProps.generated.d.ts +21 -0
- package/dist/build-components/StatusBarColor/StatusBarColor.d.ts +5 -0
- package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +54 -0
- package/dist/build-components/index.d.ts +4 -1
- package/dist/build-components/patterns.generated.d.ts +2105 -1253
- package/dist/components/AttributesEditorPanel.d.ts +1 -1
- package/dist/components/BuilderProvider.d.ts +1 -1
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +6 -6
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +4 -4
- package/dist/index.web.esm.js.map +1 -1
- package/dist/store.d.ts +4 -0
- package/dist/styles.css +1 -1
- package/dist/utils/attributeStyle.d.ts +9 -0
- package/dist/utils/extractImageStyle.d.ts +1 -1
- package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +1 -1
- package/package.json +2 -2
- package/src/DeviceMockFrame.tsx +8 -2
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/paywall-1.json +39 -34
- package/src/assets/samples/paywall-2.json +39 -20
- package/src/assets/samples/paywall-app-delete-offer.json +40 -21
- package/src/assets/samples/paywall-app-open-offer.json +40 -21
- package/src/assets/samples/paywall-back-offer.json +40 -21
- package/src/assets/samples/paywall-notification-offer.json +40 -21
- package/src/assets/samples/vpn-onboard-1.json +84 -39
- package/src/assets/samples/vpn-onboard-2.json +85 -40
- package/src/assets/samples/vpn-onboard-3.json +84 -39
- package/src/assets/samples/vpn-onboard-4.json +84 -39
- package/src/assets/samples/vpn-onboard-5.json +102 -55
- package/src/assets/samples/vpn-onboard-6.json +87 -38
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +24 -22
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +9 -4
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +19 -7
- package/src/attributes-editor/useAttributesEditorModel.ts +23 -17
- package/src/build-components/BackgroundImage/pattern.json +9 -7
- package/src/build-components/CarouselDots/CarouselDots.tsx +12 -11
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +3 -1
- package/src/build-components/Image/ImageProps.generated.ts +2 -4
- package/src/build-components/Image/pattern.json +12 -25
- package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +39 -0
- package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +71 -0
- package/src/build-components/NavigationBarColor/pattern.json +34 -0
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +8 -10
- package/src/build-components/OnboardDot/OnboardDot.tsx +12 -10
- package/src/build-components/OnboardImage/OnboardImage.tsx +1 -1
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -3
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +3 -1
- package/src/build-components/RenderNode.generated.tsx +15 -0
- package/src/build-components/Separator/Separator.tsx +41 -0
- package/src/build-components/Separator/SeparatorProps.generated.ts +26 -0
- package/src/build-components/Separator/pattern.json +59 -0
- package/src/build-components/StatusBarColor/StatusBarColor.tsx +39 -0
- package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +71 -0
- package/src/build-components/StatusBarColor/pattern.json +34 -0
- package/src/build-components/Text/pattern.json +45 -38
- package/src/build-components/index.ts +15 -0
- package/src/build-components/patterns.generated.ts +2149 -1272
- package/src/build-components/useNode.ts +24 -25
- package/src/components/AttributesEditorPanel.tsx +4 -5
- package/src/components/Builder.tsx +1 -2
- package/src/components/BuilderProvider.tsx +40 -3
- package/src/components/JsonTextEditor.tsx +2 -2
- package/src/components/LoadingComponent.tsx +1 -1
- package/src/components/RenderErrorBoundary.tsx +1 -3
- package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +3 -3
- package/src/modals/BenefitPresetsModal.tsx +1 -1
- package/src/modals/ProductPresetsModal.tsx +1 -1
- package/src/pages/DebugJsonPage.tsx +7 -4
- package/src/pages/ProjectDebug.tsx +1 -1
- package/src/pages/ProjectPage.tsx +31 -32
- package/src/pages/ProjectValidationPage.tsx +2 -2
- package/src/store.ts +13 -0
- package/src/styles/layout/_builder.scss +6 -0
- package/src/utils/__special_exceptions.ts +5 -5
- package/src/utils/analyseNode.ts +2 -2
- package/src/utils/analyseNodeByPatterns.ts +10 -9
- package/src/utils/analyseNodeStructural.ts +1 -1
- package/src/utils/attributeStyle.ts +26 -0
- package/src/utils/extractImageStyle.ts +17 -13
- package/src/utils/extractTextStyle/extractTextStyle.ts +7 -7
- package/src/utils/extractTextStyle/extractTextStyleNative.ts +10 -10
- package/src/utils/extractViewStyle/extractViewStyle.ts +8 -11
- package/src/utils/extractViewStyle/extractViewStyleNative.ts +19 -19
- package/src/utils/loadFontFamily.ts +14 -19
- package/src/utils/logRenderStore.ts +5 -4
- package/src/utils/nodeTree.ts +1 -1
- package/src/utils/patterns.ts +26 -31
- package/src/utils/repairNodeKeys.ts +5 -7
- package/src/utils/wrapNodeInMain.ts +3 -3
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { NodeData, NodeDefaultAttribute } from '../types/Node';
|
|
2
2
|
import { getDefaultsForType } from '../utils/patterns';
|
|
3
3
|
|
|
4
|
+
type WithStyleBags = {
|
|
5
|
+
style?: Record<string, unknown>;
|
|
6
|
+
styles?: Record<string, unknown>;
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
export default function useNode<
|
|
5
10
|
T extends NodeDefaultAttribute = NodeDefaultAttribute,
|
|
6
11
|
>(node: NodeData<T>): NodeData<T> {
|
|
7
12
|
const type = node?.type;
|
|
8
13
|
const defaults = getDefaultsForType(type) as Partial<T> | undefined;
|
|
9
14
|
if (!defaults) return node;
|
|
10
|
-
const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T &
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
style?: Record<string, unknown>;
|
|
16
|
-
styles?: Record<string, unknown>;
|
|
17
|
-
};
|
|
18
|
-
// Merge style from both defaults.style and defaults.styles (for schemaVersion=2 compatibility)
|
|
15
|
+
const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T &
|
|
16
|
+
WithStyleBags;
|
|
17
|
+
const defaultAttributes = defaults as T & WithStyleBags;
|
|
18
|
+
// Merge styles from both defaults and node (schemaVersion=2 uses `styles` only).
|
|
19
|
+
// Read legacy `style` for back-compat with old persisted data, but output only `styles`.
|
|
19
20
|
const defaultStyle = {
|
|
20
|
-
...(defaultAttributes?.styles ?? {}),
|
|
21
21
|
...(defaultAttributes?.style ?? {}),
|
|
22
|
+
...(defaultAttributes?.styles ?? {}),
|
|
22
23
|
};
|
|
23
|
-
// Merge node style from both node.attributes.style and node.attributes.styles (preferring styles)
|
|
24
24
|
const nodeStyle = {
|
|
25
25
|
...(nodeAttributes?.style ?? {}),
|
|
26
26
|
...(nodeAttributes?.styles ?? {}),
|
|
@@ -29,22 +29,21 @@ export default function useNode<
|
|
|
29
29
|
...defaultStyle,
|
|
30
30
|
...nodeStyle,
|
|
31
31
|
};
|
|
32
|
-
const
|
|
33
|
-
...(defaultAttributes as
|
|
34
|
-
...(nodeAttributes as
|
|
35
|
-
// Deep merge
|
|
36
|
-
//
|
|
37
|
-
style: mergedStyle,
|
|
32
|
+
const mergedRecord: Record<string, unknown> = {
|
|
33
|
+
...(defaultAttributes as Record<string, unknown>),
|
|
34
|
+
...(nodeAttributes as Record<string, unknown>),
|
|
35
|
+
// Deep merge styles so default style values aren't lost when the node provides partial overrides.
|
|
36
|
+
// Only use `styles` (schemaVersion=2). Remove legacy `style` to avoid validator rejection.
|
|
38
37
|
styles: mergedStyle,
|
|
39
|
-
}
|
|
38
|
+
};
|
|
39
|
+
// Remove legacy `style` key if it was inherited from defaults or node attributes.
|
|
40
|
+
delete mergedRecord.style;
|
|
40
41
|
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(
|
|
44
|
-
Object.keys((mergedAttributes as any).style).length === 0
|
|
42
|
+
typeof mergedRecord.styles === 'object' &&
|
|
43
|
+
mergedRecord.styles != null &&
|
|
44
|
+
Object.keys(mergedRecord.styles as Record<string, unknown>).length === 0
|
|
45
45
|
) {
|
|
46
|
-
delete
|
|
47
|
-
delete (mergedAttributes as any).styles;
|
|
46
|
+
delete mergedRecord.styles;
|
|
48
47
|
}
|
|
49
|
-
return { ...node, attributes:
|
|
48
|
+
return { ...node, attributes: mergedRecord as T };
|
|
50
49
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { AttributesEditor } from '../AttributesEditor';
|
|
2
|
-
import type { Node } from '../types/Node';
|
|
2
|
+
import type { Node, NodeData } from '../types/Node';
|
|
3
3
|
import type { ProjectColors } from '../types/Project';
|
|
4
4
|
import { useLogRender } from '../utils/useLogRender';
|
|
5
5
|
import { useRenderStore } from '../store';
|
|
6
6
|
import { findNodeByKey } from '../utils/nodeTree';
|
|
7
7
|
|
|
8
8
|
interface AttributesEditorPanelProps {
|
|
9
|
-
attributes:
|
|
9
|
+
attributes: Node;
|
|
10
10
|
onChange: (data: Node) => void;
|
|
11
11
|
projectColors?: ProjectColors;
|
|
12
12
|
}
|
|
@@ -25,7 +25,7 @@ export function AttributesEditorPanel({
|
|
|
25
25
|
|
|
26
26
|
const currentKey =
|
|
27
27
|
typeof current === 'object' && !Array.isArray(current) && 'key' in current
|
|
28
|
-
? (
|
|
28
|
+
? (current as NodeData).key
|
|
29
29
|
: undefined;
|
|
30
30
|
const resolvedCurrent =
|
|
31
31
|
currentKey && attributes
|
|
@@ -46,14 +46,13 @@ export function AttributesEditorPanel({
|
|
|
46
46
|
});
|
|
47
47
|
return changed ? arr : root;
|
|
48
48
|
}
|
|
49
|
-
const data = root as
|
|
49
|
+
const data = root as NodeData;
|
|
50
50
|
if ('children' in data) {
|
|
51
51
|
const prev = data.children;
|
|
52
52
|
const replaced = Array.isArray(prev)
|
|
53
53
|
? prev.map((c: Node) => replaceNode(c, target, next))
|
|
54
54
|
: replaceNode(prev as Node, target, next);
|
|
55
55
|
if (replaced !== prev) {
|
|
56
|
-
data.children = replaced;
|
|
57
56
|
return { ...data, children: replaced } as Node;
|
|
58
57
|
}
|
|
59
58
|
}
|
|
@@ -452,14 +452,13 @@ export function Builder({
|
|
|
452
452
|
});
|
|
453
453
|
return changed ? arr : root;
|
|
454
454
|
}
|
|
455
|
-
const data = root as
|
|
455
|
+
const data = root as NodeData;
|
|
456
456
|
if ('children' in data) {
|
|
457
457
|
const prev = data.children;
|
|
458
458
|
const replaced = Array.isArray(prev)
|
|
459
459
|
? prev.map((c: Node) => replaceNode(c, target, next))
|
|
460
460
|
: replaceNode(prev as Node, target, next);
|
|
461
461
|
if (replaced !== prev) {
|
|
462
|
-
data.children = replaced;
|
|
463
462
|
return { ...data, children: replaced } as Node;
|
|
464
463
|
}
|
|
465
464
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, useContext,
|
|
1
|
+
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
2
|
import type { Product } from '../paywall/types/paywall-types';
|
|
3
3
|
import type { PaywallBenefits } from '../paywall/types/benefits';
|
|
4
4
|
import type { AppConfig } from '../types/PreviewConfig';
|
|
@@ -66,7 +66,7 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
|
|
|
66
66
|
projectColors:
|
|
67
67
|
params?.projectColors && typeof params.projectColors === 'object'
|
|
68
68
|
? (params.projectColors as ProjectColors)
|
|
69
|
-
:
|
|
69
|
+
: defaultProjectColors,
|
|
70
70
|
fonts: Array.isArray(params?.fonts) ? (params.fonts as Fonts) : undefined,
|
|
71
71
|
appFont: params?.appFont,
|
|
72
72
|
platform: params?.platform === 'native' ? 'native' : 'web',
|
|
@@ -95,12 +95,49 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
const defaultProjectColors: Readonly<ProjectColors> = {
|
|
99
|
+
STATIC_COLORS: {
|
|
100
|
+
BLACK: '#000',
|
|
101
|
+
WHITE: '#FFFFFF',
|
|
102
|
+
TRANSPARENT: '#ffffff00',
|
|
103
|
+
ONBOARD_DOT_INACTIVE: '#E4E5E7',
|
|
104
|
+
ONBOARD_DOT_ACTIVE: '#007AFF',
|
|
105
|
+
BUTTON_PRIMARY_BACKGROUND: '#0066FF',
|
|
106
|
+
BUTTON_PRIMARY_TEXT: '#FFFFFF',
|
|
107
|
+
LINK_COLOR: '#1778F2',
|
|
108
|
+
SEPARATOR_COLOR: '#44454D',
|
|
109
|
+
},
|
|
110
|
+
THEME_COLORS: {
|
|
111
|
+
light: {
|
|
112
|
+
TEXT: '#161827',
|
|
113
|
+
BACKGROUND: '#F4F5FF',
|
|
114
|
+
ICON: '#0450E2',
|
|
115
|
+
LINE: '#E9EBF9',
|
|
116
|
+
ONBOARD_TITLE: '#161827',
|
|
117
|
+
ONBOARD_SUBTITLE: '#44454D',
|
|
118
|
+
BUTTON_SECONDARY_TEXT: '#81838F',
|
|
119
|
+
FOOTER_TEXT: '#81838F',
|
|
120
|
+
},
|
|
121
|
+
dark: {
|
|
122
|
+
TEXT: '#161827',
|
|
123
|
+
BACKGROUND: '#F4F5FF',
|
|
124
|
+
ICON: '#0450E2',
|
|
125
|
+
LINE: '#E9EBF9',
|
|
126
|
+
ONBOARD_TITLE: '#FDFDFD',
|
|
127
|
+
ONBOARD_SUBTITLE: '#C7C7C7',
|
|
128
|
+
BUTTON_SECONDARY_TEXT: '#A9AAAC',
|
|
129
|
+
FOOTER_TEXT: '#A2A4B1',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
} as const;
|
|
133
|
+
|
|
134
|
+
export function useBuilderParams(): Readonly<BuilderProviderParams> {
|
|
99
135
|
return (
|
|
100
136
|
useContext(BuilderContext) ?? {
|
|
101
137
|
products: [],
|
|
102
138
|
benefits: {},
|
|
103
139
|
platform: 'web',
|
|
140
|
+
projectColors: defaultProjectColors,
|
|
104
141
|
}
|
|
105
142
|
);
|
|
106
143
|
}
|
|
@@ -88,8 +88,8 @@ export function JsonTextEditor({
|
|
|
88
88
|
const nextValue =
|
|
89
89
|
isRecord(parsed) && 'data' in parsed
|
|
90
90
|
? {
|
|
91
|
-
...
|
|
92
|
-
data: wrapNodeInMain(
|
|
91
|
+
...parsed,
|
|
92
|
+
data: wrapNodeInMain(parsed.data as Node),
|
|
93
93
|
}
|
|
94
94
|
: wrapNodeInMain(parsed as Node);
|
|
95
95
|
|
|
@@ -4,7 +4,7 @@ import loadingAnimation from '../assets/loading_animation.json';
|
|
|
4
4
|
export function LoadingComponent() {
|
|
5
5
|
return (
|
|
6
6
|
<div className="rb-loading">
|
|
7
|
-
<Lottie animationData={loadingAnimation
|
|
7
|
+
<Lottie animationData={loadingAnimation} loop autoplay />
|
|
8
8
|
</div>
|
|
9
9
|
);
|
|
10
10
|
}
|
|
@@ -43,9 +43,7 @@ export class RenderErrorBoundary extends React.Component<
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
46
|
-
const componentStack =
|
|
47
|
-
(info as unknown as { componentStack?: string })?.componentStack ??
|
|
48
|
-
undefined;
|
|
46
|
+
const componentStack = info.componentStack ?? undefined;
|
|
49
47
|
|
|
50
48
|
this.setState({
|
|
51
49
|
error,
|
|
@@ -77,7 +77,7 @@ function getStyleSubSchemaKeys(
|
|
|
77
77
|
schema: Record<string, unknown> | undefined,
|
|
78
78
|
): Set<string> {
|
|
79
79
|
if (!schema) return new Set();
|
|
80
|
-
const style =
|
|
80
|
+
const style = schema.style;
|
|
81
81
|
if (!isPlainObject(style)) return new Set();
|
|
82
82
|
return new Set(Object.keys(style));
|
|
83
83
|
}
|
|
@@ -195,7 +195,7 @@ export const migration_1_1_2_extract_component_attributes_from_style: Migration
|
|
|
195
195
|
run: (project: Project): Project => {
|
|
196
196
|
const styleKeys = buildStyleKeySet();
|
|
197
197
|
const normalized = normalizeStyleAttributes(
|
|
198
|
-
project.data as
|
|
198
|
+
project.data as Node,
|
|
199
199
|
styleKeys,
|
|
200
200
|
);
|
|
201
201
|
|
|
@@ -205,7 +205,7 @@ export const migration_1_1_2_extract_component_attributes_from_style: Migration
|
|
|
205
205
|
return {
|
|
206
206
|
...project,
|
|
207
207
|
version: '1.1.2',
|
|
208
|
-
data: migrated as
|
|
208
|
+
data: migrated as Project['data'],
|
|
209
209
|
};
|
|
210
210
|
},
|
|
211
211
|
};
|
|
@@ -34,7 +34,7 @@ function normalizeBenefits(raw: unknown): PaywallBenefits {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function getPresetMap(): PresetMap {
|
|
37
|
-
const raw = presetsJson as
|
|
37
|
+
const raw = presetsJson as PresetMap;
|
|
38
38
|
const safe: PresetMap = {};
|
|
39
39
|
Object.entries(raw ?? {}).forEach(([key, map]) => {
|
|
40
40
|
const normalizedKey = typeof key === 'string' ? key.trim() : '';
|
|
@@ -25,7 +25,7 @@ function normalizeProduct(p: Product): Product {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function getPresetMap(): PresetMap {
|
|
28
|
-
const raw = presetsJson as
|
|
28
|
+
const raw = presetsJson as PresetMap;
|
|
29
29
|
const safe: PresetMap = {};
|
|
30
30
|
Object.entries(raw ?? {}).forEach(([key, list]) => {
|
|
31
31
|
if (!key || typeof key !== 'string') return;
|
|
@@ -54,7 +54,8 @@ export function DebugJsonPage({
|
|
|
54
54
|
typeof setAppConfig === 'function' && typeof appConfig?.theme === 'string';
|
|
55
55
|
const canToggleRtl =
|
|
56
56
|
typeof setAppConfig === 'function' &&
|
|
57
|
-
|
|
57
|
+
appConfig != null &&
|
|
58
|
+
'isRtl' in appConfig;
|
|
58
59
|
|
|
59
60
|
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
|
60
61
|
typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
@@ -64,7 +65,7 @@ export function DebugJsonPage({
|
|
|
64
65
|
// - raw Node JSON
|
|
65
66
|
// - Project wrapper JSON { name, version, data: Node }
|
|
66
67
|
if (isRecord(value) && 'data' in value) {
|
|
67
|
-
return
|
|
68
|
+
return value.data as Node;
|
|
68
69
|
}
|
|
69
70
|
return value as Node;
|
|
70
71
|
};
|
|
@@ -222,7 +223,9 @@ export function DebugJsonPage({
|
|
|
222
223
|
{canToggleRtl ? (
|
|
223
224
|
<Checkbox
|
|
224
225
|
label="Is RTL"
|
|
225
|
-
checked={Boolean(
|
|
226
|
+
checked={Boolean(
|
|
227
|
+
(appConfig as AppConfig & { isRtl?: boolean })?.isRtl,
|
|
228
|
+
)}
|
|
226
229
|
onChange={(checked) =>
|
|
227
230
|
setAppConfig!({ ...(appConfig as AppConfig), isRtl: checked })
|
|
228
231
|
}
|
|
@@ -245,7 +248,7 @@ export function DebugJsonPage({
|
|
|
245
248
|
<div className="localication-modal__editor">
|
|
246
249
|
<JsonTextEditor
|
|
247
250
|
rootName="node"
|
|
248
|
-
value={data ?? ({} as
|
|
251
|
+
value={data ?? ({} as Record<string, unknown>)}
|
|
249
252
|
onChange={(next) => {
|
|
250
253
|
const nodeCandidate = extractNode(next);
|
|
251
254
|
const processed = analyseAndProccess(
|
|
@@ -300,7 +300,7 @@ export function ProjectDebug({
|
|
|
300
300
|
className="rb-project-debug__preview"
|
|
301
301
|
aria-label="Preview"
|
|
302
302
|
style={{
|
|
303
|
-
['--rb-canvas-bg' as
|
|
303
|
+
['--rb-canvas-bg' as string]: canvasBg ?? 'none',
|
|
304
304
|
}}
|
|
305
305
|
>
|
|
306
306
|
<RenderErrorBoundary
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import type { Node } from '../types/Node';
|
|
2
|
+
import type { Node, NodeData } from '../types/Node';
|
|
3
3
|
import type { Project, ProjectColors } from '../types/Project';
|
|
4
4
|
import { ToastContainer, toast } from 'react-toastify';
|
|
5
5
|
import { RenderPage } from '../RenderPage';
|
|
@@ -248,8 +248,8 @@ export function ProjectPage({
|
|
|
248
248
|
if (bypassValidation) {
|
|
249
249
|
// Best-effort: let the user continue with the raw data even if invalid.
|
|
250
250
|
// This may still crash the preview, but it unblocks users for debugging.
|
|
251
|
-
setEditorData(activeProject.data as
|
|
252
|
-
setCurrent(activeProject.data as
|
|
251
|
+
setEditorData(activeProject.data as Node);
|
|
252
|
+
setCurrent(activeProject.data as Node);
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
255
|
if (isEmptyProjectData) {
|
|
@@ -346,8 +346,8 @@ export function ProjectPage({
|
|
|
346
346
|
onContinueWithoutValidation={() => {
|
|
347
347
|
setBypassValidation(true);
|
|
348
348
|
setMigrationGate(null);
|
|
349
|
-
setEditorData(activeProject.data as
|
|
350
|
-
setCurrent(activeProject.data as
|
|
349
|
+
setEditorData(activeProject.data as Node);
|
|
350
|
+
setCurrent(activeProject.data as Node);
|
|
351
351
|
setMinLoadingDelayDone(true);
|
|
352
352
|
}}
|
|
353
353
|
onMigrateNow={() => {
|
|
@@ -366,12 +366,12 @@ export function ProjectPage({
|
|
|
366
366
|
isNodeLike(activeProject) &&
|
|
367
367
|
!(
|
|
368
368
|
isRecord(activeProject) &&
|
|
369
|
-
typeof
|
|
369
|
+
typeof activeProject.version === 'string'
|
|
370
370
|
)
|
|
371
371
|
? getDefaultProject({
|
|
372
372
|
name: `imported-${Math.random().toString(36).slice(2, 8)}`,
|
|
373
373
|
version: CURRENT_PROJECT_VERSION,
|
|
374
|
-
data: activeProject as
|
|
374
|
+
data: activeProject as Node,
|
|
375
375
|
})
|
|
376
376
|
: activeProject;
|
|
377
377
|
|
|
@@ -381,8 +381,8 @@ export function ProjectPage({
|
|
|
381
381
|
setOverrideProject(migratedProject);
|
|
382
382
|
setBypassValidation(true);
|
|
383
383
|
setMigrationGate(null);
|
|
384
|
-
setEditorData(migratedProject.data as
|
|
385
|
-
setCurrent(migratedProject.data as
|
|
384
|
+
setEditorData(migratedProject.data as Node);
|
|
385
|
+
setCurrent(migratedProject.data as Node);
|
|
386
386
|
setMinLoadingDelayDone(true);
|
|
387
387
|
} finally {
|
|
388
388
|
setIsMigrating(false);
|
|
@@ -400,20 +400,18 @@ export function ProjectPage({
|
|
|
400
400
|
): v is 'paywall' | 'onboard' | 'other' =>
|
|
401
401
|
v === 'paywall' || v === 'onboard' || v === 'other';
|
|
402
402
|
|
|
403
|
+
const activeRecord = isRecord(activeProject) ? activeProject : null;
|
|
403
404
|
const fixedName =
|
|
404
|
-
typeof
|
|
405
|
-
String(
|
|
406
|
-
? String(
|
|
405
|
+
typeof activeRecord?.name === 'string' &&
|
|
406
|
+
String(activeRecord.name).trim()
|
|
407
|
+
? String(activeRecord.name).trim()
|
|
407
408
|
: `imported-${Math.random().toString(36).slice(2, 8)}`;
|
|
408
409
|
|
|
409
|
-
const activeAny = (
|
|
410
|
-
isRecord(activeProject) ? activeProject : null
|
|
411
|
-
) as Record<string, unknown> | null;
|
|
412
410
|
const nodeCandidate = (
|
|
413
|
-
|
|
414
|
-
?
|
|
411
|
+
activeRecord && 'data' in activeRecord
|
|
412
|
+
? activeRecord.data
|
|
415
413
|
: isNodeLike(activeProject)
|
|
416
|
-
? (activeProject as
|
|
414
|
+
? (activeProject as Node)
|
|
417
415
|
: null
|
|
418
416
|
) as Node | null;
|
|
419
417
|
|
|
@@ -421,10 +419,12 @@ export function ProjectPage({
|
|
|
421
419
|
name: fixedName,
|
|
422
420
|
version: CURRENT_PROJECT_VERSION,
|
|
423
421
|
data: nodeCandidate,
|
|
424
|
-
appConfig:
|
|
425
|
-
projectColors:
|
|
426
|
-
|
|
427
|
-
|
|
422
|
+
appConfig: activeRecord?.appConfig,
|
|
423
|
+
projectColors: activeRecord?.projectColors as
|
|
424
|
+
| ProjectColors
|
|
425
|
+
| undefined,
|
|
426
|
+
type: isAllowedProjectType(activeRecord?.type)
|
|
427
|
+
? (activeRecord.type as 'paywall' | 'onboard' | 'other')
|
|
428
428
|
: undefined,
|
|
429
429
|
});
|
|
430
430
|
|
|
@@ -452,8 +452,8 @@ export function ProjectPage({
|
|
|
452
452
|
setBypassValidation(true);
|
|
453
453
|
setValidationError(null);
|
|
454
454
|
setValidationErrorStack(null);
|
|
455
|
-
setEditorData(activeProject.data as
|
|
456
|
-
setCurrent(activeProject.data as
|
|
455
|
+
setEditorData(activeProject.data as Node);
|
|
456
|
+
setCurrent(activeProject.data as Node);
|
|
457
457
|
setMinLoadingDelayDone(true);
|
|
458
458
|
}}
|
|
459
459
|
onSaveEditedRawData={(nextRawData) => {
|
|
@@ -470,10 +470,9 @@ export function ProjectPage({
|
|
|
470
470
|
let nextVersion: string | undefined;
|
|
471
471
|
|
|
472
472
|
if (isRecord(parsed) && 'data' in parsed) {
|
|
473
|
-
nodeCandidate =
|
|
474
|
-
const maybeName =
|
|
475
|
-
const maybeVersion =
|
|
476
|
-
.version;
|
|
473
|
+
nodeCandidate = parsed.data;
|
|
474
|
+
const maybeName = parsed.name;
|
|
475
|
+
const maybeVersion = parsed.version;
|
|
477
476
|
if (typeof maybeName === 'string') nextName = maybeName;
|
|
478
477
|
if (typeof maybeVersion === 'string')
|
|
479
478
|
nextVersion = maybeVersion;
|
|
@@ -602,7 +601,7 @@ export function ProjectPage({
|
|
|
602
601
|
style={{
|
|
603
602
|
// Set as a CSS variable so `.dark .split-right` can override it.
|
|
604
603
|
|
|
605
|
-
['--rb-canvas-bg' as
|
|
604
|
+
['--rb-canvas-bg' as string]: `url(${getImage(
|
|
606
605
|
TribeAssetName.Background,
|
|
607
606
|
)})`,
|
|
608
607
|
}}
|
|
@@ -632,7 +631,7 @@ export function ProjectPage({
|
|
|
632
631
|
previewMode,
|
|
633
632
|
selectedKey:
|
|
634
633
|
current && typeof current === 'object' && 'key' in current
|
|
635
|
-
? ((current as
|
|
634
|
+
? ((current as NodeData).key as string | undefined)
|
|
636
635
|
: undefined,
|
|
637
636
|
}}
|
|
638
637
|
/>
|
|
@@ -669,9 +668,9 @@ export function ProjectPage({
|
|
|
669
668
|
data &&
|
|
670
669
|
typeof data === 'object' &&
|
|
671
670
|
!Array.isArray(data) &&
|
|
672
|
-
'key' in
|
|
671
|
+
'key' in data
|
|
673
672
|
) {
|
|
674
|
-
nodeKey = (data as
|
|
673
|
+
nodeKey = (data as NodeData).key;
|
|
675
674
|
}
|
|
676
675
|
logger.verbose(
|
|
677
676
|
'ProjectPage',
|
|
@@ -43,12 +43,12 @@ export function ProjectValidationPage({
|
|
|
43
43
|
typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
44
44
|
|
|
45
45
|
const nodeCandidate: unknown =
|
|
46
|
-
isRecord(parsed) && 'data' in parsed ?
|
|
46
|
+
isRecord(parsed) && 'data' in parsed ? parsed.data : parsed;
|
|
47
47
|
|
|
48
48
|
const wrapped = wrapNodeInMain(nodeCandidate as Node);
|
|
49
49
|
const nextValue =
|
|
50
50
|
isRecord(parsed) && 'data' in parsed
|
|
51
|
-
? { ...
|
|
51
|
+
? { ...parsed, data: wrapped }
|
|
52
52
|
: wrapped;
|
|
53
53
|
|
|
54
54
|
setJsonText(JSON.stringify(nextValue, null, 2));
|
package/src/store.ts
CHANGED
|
@@ -78,6 +78,12 @@ type RenderStore = {
|
|
|
78
78
|
markFontLoaded: (fontFamily: string) => void;
|
|
79
79
|
listMaxNested: number;
|
|
80
80
|
setListMaxNested: (depth: number) => void;
|
|
81
|
+
|
|
82
|
+
// OS bar color overrides (set by StatusBarColor / NavigationBarColor build components)
|
|
83
|
+
statusBarOverrideColor: string | null;
|
|
84
|
+
setStatusBarOverrideColor: (color: string | null) => void;
|
|
85
|
+
navBarOverrideColor: string | null;
|
|
86
|
+
setNavBarOverrideColor: (color: string | null) => void;
|
|
81
87
|
};
|
|
82
88
|
|
|
83
89
|
export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
@@ -240,6 +246,13 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
|
240
246
|
set({
|
|
241
247
|
listMaxNested: Number.isFinite(depth) && depth > 0 ? depth : 5,
|
|
242
248
|
}),
|
|
249
|
+
|
|
250
|
+
// OS bar color overrides
|
|
251
|
+
statusBarOverrideColor: null,
|
|
252
|
+
setStatusBarOverrideColor: (color) =>
|
|
253
|
+
set({ statusBarOverrideColor: color }),
|
|
254
|
+
navBarOverrideColor: null,
|
|
255
|
+
setNavBarOverrideColor: (color) => set({ navBarOverrideColor: color }),
|
|
243
256
|
}),
|
|
244
257
|
{
|
|
245
258
|
name: 'render-store',
|
|
@@ -175,6 +175,12 @@
|
|
|
175
175
|
> :not(:first-child) {
|
|
176
176
|
margin-left: sizes.$spaceComfy;
|
|
177
177
|
}
|
|
178
|
+
// When the parent button has sort controls (↑↓), the button text is pushed
|
|
179
|
+
// right by the sort-controls width (32px). Increase child indent so children
|
|
180
|
+
// stay visually nested under the parent label, not under the sort icons.
|
|
181
|
+
> .builder__button:first-child:has(.builder__sort-controls) ~ * {
|
|
182
|
+
margin-left: 32px + sizes.$spaceComfy;
|
|
183
|
+
}
|
|
178
184
|
&::before {
|
|
179
185
|
content: '';
|
|
180
186
|
position: absolute;
|
|
@@ -19,7 +19,7 @@ function looksLikeSelectOptionObject(
|
|
|
19
19
|
if (keys.length < 1 || keys.length > 3) return false;
|
|
20
20
|
const allowed = new Set(['value', 'label', 'id']);
|
|
21
21
|
for (const k of keys) if (!allowed.has(k)) return false;
|
|
22
|
-
const v =
|
|
22
|
+
const v = value.value;
|
|
23
23
|
return (
|
|
24
24
|
v == null ||
|
|
25
25
|
typeof v === 'string' ||
|
|
@@ -61,10 +61,10 @@ export function normalizeNodeForValidation(
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
if (isNodeArray(node)) {
|
|
64
|
-
const nodeArray = node as
|
|
64
|
+
const nodeArray = node as Node<NodeDefaultAttribute>[];
|
|
65
65
|
return nodeArray.map(
|
|
66
66
|
normalizeNodeForValidation,
|
|
67
|
-
) as
|
|
67
|
+
) as Node<NodeDefaultAttribute>;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const recordData = node as NodeData<NodeDefaultAttribute>;
|
|
@@ -76,7 +76,7 @@ export function normalizeNodeForValidation(
|
|
|
76
76
|
attributes = {};
|
|
77
77
|
}
|
|
78
78
|
attributes = isPlainObject(attributes)
|
|
79
|
-
?
|
|
79
|
+
? normalizeUnknownValue(attributes)
|
|
80
80
|
: attributes;
|
|
81
81
|
|
|
82
82
|
const children =
|
|
@@ -88,7 +88,7 @@ export function normalizeNodeForValidation(
|
|
|
88
88
|
|
|
89
89
|
return {
|
|
90
90
|
...recordData,
|
|
91
|
-
attributes: attributes as
|
|
91
|
+
attributes: attributes as NodeDefaultAttribute,
|
|
92
92
|
children,
|
|
93
93
|
};
|
|
94
94
|
}
|
package/src/utils/analyseNode.ts
CHANGED
|
@@ -21,10 +21,10 @@ function assignMissingKeys(
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (isNodeArray(node)) {
|
|
24
|
-
const nodeArray = node as
|
|
24
|
+
const nodeArray = node as Node<NodeDefaultAttribute>[];
|
|
25
25
|
return nodeArray.map((child) =>
|
|
26
26
|
assignMissingKeys(child, usedKeys),
|
|
27
|
-
) as
|
|
27
|
+
) as Node<NodeDefaultAttribute>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const recordData = node as NodeData<NodeDefaultAttribute>;
|
|
@@ -70,7 +70,7 @@ function isNodeDataLike(
|
|
|
70
70
|
value: unknown,
|
|
71
71
|
): value is NodeData<NodeDefaultAttribute> {
|
|
72
72
|
if (!isPlainObject(value)) return false;
|
|
73
|
-
const maybeType =
|
|
73
|
+
const maybeType = value.type;
|
|
74
74
|
// `children` is optional in persisted JSON; treat missing as `undefined`.
|
|
75
75
|
// The structural validator handles overall shape; the pattern validator should
|
|
76
76
|
// focus on types/attributes/children rules, not require the key to exist.
|
|
@@ -316,9 +316,10 @@ function validateCustomObjectValue(
|
|
|
316
316
|
return fail(`Expected object for type "${typeName}"`, path);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
const valueRecord = value as Record<string, unknown>;
|
|
319
320
|
for (const [fieldName, fieldSpec] of Object.entries(schema)) {
|
|
320
|
-
if (!(fieldName in
|
|
321
|
-
const fieldValue =
|
|
321
|
+
if (!(fieldName in valueRecord)) continue;
|
|
322
|
+
const fieldValue = valueRecord[fieldName];
|
|
322
323
|
const fieldPath = joinPath(path, fieldName);
|
|
323
324
|
const res = validateAttributeValue(
|
|
324
325
|
componentType,
|
|
@@ -387,8 +388,9 @@ function validateAttributesByPattern(
|
|
|
387
388
|
{}) as AttributeSchema;
|
|
388
389
|
const styleSchema = getStyleSubSchema(schema);
|
|
389
390
|
|
|
390
|
-
const
|
|
391
|
-
const
|
|
391
|
+
const attrsRecord = attrs as Record<string, unknown>;
|
|
392
|
+
const maybeStyle = attrsRecord.style;
|
|
393
|
+
const maybeStyles = attrsRecord.styles;
|
|
392
394
|
|
|
393
395
|
// schemaVersion=2 requires `attributes.styles` (plural), not `attributes.style` (singular)
|
|
394
396
|
if (maybeStyle != null) {
|
|
@@ -403,9 +405,8 @@ function validateAttributesByPattern(
|
|
|
403
405
|
if (!isPlainObject(maybeStyles)) {
|
|
404
406
|
return fail(`styles must be an object`, joinPath(path, 'styles'));
|
|
405
407
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
)) {
|
|
408
|
+
const stylesRecord = maybeStyles as Record<string, unknown>;
|
|
409
|
+
for (const [styleKey, styleValue] of Object.entries(stylesRecord)) {
|
|
409
410
|
const spec = (styleSchema?.[styleKey] ?? schema?.[styleKey]) as
|
|
410
411
|
| AttributeTypeSpec
|
|
411
412
|
| undefined;
|
|
@@ -510,7 +511,7 @@ function validateAnyNodeByPatterns(
|
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
if (isNodeArray(node)) {
|
|
513
|
-
const arr = node as
|
|
514
|
+
const arr = node as Node<NodeDefaultAttribute>[];
|
|
514
515
|
for (let i = 0; i < arr.length; i++) {
|
|
515
516
|
const res = validateAnyNodeByPatterns(arr[i], `${path}[${i}]`);
|
|
516
517
|
if (!res.valid) return res;
|
|
@@ -28,7 +28,7 @@ export function analyseNodeStructural(
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (isNodeArray(node)) {
|
|
31
|
-
const nodeArray = node as
|
|
31
|
+
const nodeArray = node as Node[];
|
|
32
32
|
for (const value of nodeArray) {
|
|
33
33
|
const res = analyseNodeStructural(value as Node<NodeDefaultAttribute>);
|
|
34
34
|
if (!res.valid) return res;
|