@developer_tribe/react-builder 1.0.8 → 1.0.9
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 +3 -0
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +1 -0
- package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -0
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +5 -0
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +1 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +1 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -0
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -0
- package/dist/build-components/Image/ImageProps.generated.d.ts +1 -0
- package/dist/build-components/Main/MainProps.generated.d.ts +1 -1
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +3 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +1 -0
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +3 -0
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +3 -0
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +3 -0
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +3 -1
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallProvider/PaywallContext.d.ts +12 -0
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +1 -0
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +1 -1
- package/dist/build-components/Text/TextProps.generated.d.ts +3 -0
- package/dist/build-components/View/ViewProps.generated.d.ts +1 -0
- package/dist/build-components/patterns.generated.d.ts +372 -374
- package/dist/components/BuilderProvider.d.ts +2 -0
- package/dist/components/ParamsProvider.d.ts +5 -0
- package/dist/components/RenderErrorBoundary.d.ts +28 -0
- package/dist/hooks/useSyncHtmlThemeClass.d.ts +7 -0
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.cjs.js +4 -4
- package/dist/index.native.cjs.js.map +1 -1
- package/dist/index.native.d.ts +1 -0
- package/dist/index.native.esm.js +4 -4
- package/dist/index.native.esm.js.map +1 -1
- package/dist/migrations/migratePipe.d.ts +14 -0
- package/dist/migrations/migrations/1.1.0_normalize_style_attributes.d.ts +2 -0
- package/dist/migrations/semver.d.ts +8 -0
- package/dist/migrations/types.d.ts +8 -0
- package/dist/mockOS/components/SubscriptionModal.d.ts +7 -0
- package/dist/mockOS/context/MockOSContextBase.d.ts +1 -0
- package/dist/mockOS/hooks/useMockIap.d.ts +3 -0
- package/dist/mockOS/index.d.ts +4 -0
- package/dist/mockOS/managers/mockOSIapManager.d.ts +6 -0
- package/dist/mockOS/managers/subscriptionManager.d.ts +10 -0
- package/dist/pages/ProjectDebug.d.ts +14 -0
- package/dist/pages/ProjectMigrationPage.d.ts +23 -0
- package/dist/pages/ProjectValidationPage.d.ts +15 -0
- package/dist/styles.css +1 -1
- package/dist/types/Device.d.ts +5 -0
- package/dist/utils/__special_exceptions.d.ts +7 -0
- package/dist/utils/getImage.d.ts +23 -0
- package/dist/utils/pasteNode.d.ts +15 -0
- package/dist/utils/patterns.d.ts +1 -2
- package/package.json +6 -2
- package/scripts/migrate-patterns-to-v2.mjs +131 -0
- package/scripts/migrate-samples-to-current.ts +79 -0
- package/scripts/prebuild/utils/createGeneratedProps.js +4 -5
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +32 -21
- package/scripts/prebuild/utils/validatePatternJson.js +12 -10
- package/src/.DS_Store +0 -0
- package/src/AttributesEditor.tsx +41 -11
- package/src/RenderPage.tsx +55 -0
- package/src/assets/.DS_Store +0 -0
- package/src/assets/devices.json +91 -0
- package/src/assets/samples/carousel-sample.json +141 -29
- package/src/assets/samples/getSamples.ts +9 -0
- package/src/assets/samples/paywall-1.json +119 -71
- package/src/assets/samples/simple-1.json +28 -16
- package/src/assets/samples/simple-2.json +157 -82
- package/src/assets/samples/unmigrated-builder1.json +42 -0
- package/src/assets/samples/unvalidated-builder1.json +49 -0
- package/src/assets/samples/unvalidated-crash1.json +19 -0
- package/src/assets/samples/unvalidated-crashcomponent1.json +16 -0
- package/src/assets/samples/vpn-onboard-1.json +91 -51
- package/src/assets/samples/vpn-onboard-2.json +318 -278
- package/src/assets/samples/vpn-onboard-3.json +286 -252
- package/src/assets/samples/vpn-onboard-4.json +286 -252
- package/src/assets/samples/vpn-onboard-5.json +434 -374
- package/src/assets/samples/vpn-onboard-6.json +290 -250
- package/src/attributes-editor/Field.tsx +1 -1
- package/src/attributes-editor/LayoutPreviewPicker.tsx +5 -2
- package/src/build-components/BIcon/BIconProps.generated.ts +3 -0
- package/src/build-components/BIcon/pattern.json +12 -9
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +3 -1
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +1 -0
- package/src/build-components/BackgroundImage/pattern.json +25 -16
- package/src/build-components/Button/Button.tsx +26 -3
- package/src/build-components/Button/ButtonProps.generated.ts +1 -0
- package/src/build-components/Button/pattern.json +10 -6
- package/src/build-components/Carousel/CarouselProps.generated.ts +5 -0
- package/src/build-components/Carousel/pattern.json +19 -8
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +1 -0
- package/src/build-components/CarouselButtons/pattern.json +11 -5
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +1 -0
- package/src/build-components/CarouselDots/pattern.json +5 -4
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -0
- package/src/build-components/CarouselItem/pattern.json +5 -4
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +44 -2
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -0
- package/src/build-components/Image/Image.tsx +2 -1
- package/src/build-components/Image/ImageProps.generated.ts +1 -0
- package/src/build-components/Image/pattern.json +11 -5
- package/src/build-components/Main/MainProps.generated.ts +1 -1
- package/src/build-components/Main/pattern.json +12 -9
- package/src/build-components/Onboard/OnboardProps.generated.ts +1 -0
- package/src/build-components/Onboard/pattern.json +14 -9
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +1 -0
- package/src/build-components/OnboardButton/pattern.json +5 -4
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +1 -0
- package/src/build-components/OnboardButtons/pattern.json +5 -4
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +1 -0
- package/src/build-components/OnboardDot/pattern.json +5 -4
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +3 -0
- package/src/build-components/OnboardFooter/pattern.json +8 -5
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -0
- package/src/build-components/OnboardImage/pattern.json +7 -4
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +1 -0
- package/src/build-components/OnboardItem/pattern.json +18 -9
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +3 -0
- package/src/build-components/OnboardProvider/pattern.json +21 -6
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +3 -0
- package/src/build-components/OnboardSubtitle/pattern.json +10 -6
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +3 -0
- package/src/build-components/OnboardTitle/pattern.json +11 -7
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +1 -1
- package/src/build-components/PaywallBackground/pattern.json +5 -4
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +6 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +3 -1
- package/src/build-components/PaywallCloseButton/pattern.json +15 -12
- package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +0 -1
- package/src/build-components/PaywallOptions/PaywallOptions.tsx +3 -2
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
- package/src/build-components/PaywallOptions/pattern.json +14 -11
- package/src/build-components/PaywallProvider/PaywallContext.ts +25 -0
- package/src/build-components/PaywallProvider/PaywallProvider.tsx +102 -5
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -1
- package/src/build-components/PaywallProvider/pattern.json +11 -8
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +7 -0
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +1 -0
- package/src/build-components/PaywallSubscribeButton/pattern.json +16 -13
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +1 -1
- package/src/build-components/RadioButton/pattern.json +5 -4
- package/src/build-components/Text/Text.tsx +107 -4
- package/src/build-components/Text/TextProps.generated.ts +3 -0
- package/src/build-components/Text/pattern.json +19 -4
- package/src/build-components/View/ViewProps.generated.ts +1 -0
- package/src/build-components/View/pattern.json +28 -13
- package/src/build-components/other.tsx +15 -0
- package/src/build-components/patterns.generated.ts +340 -235
- package/src/build-components/useNode.ts +22 -3
- package/src/components/Builder.tsx +20 -6
- package/src/components/BuilderButton.tsx +75 -38
- package/src/components/BuilderProvider.tsx +22 -2
- package/src/components/DeviceButton.tsx +12 -5
- package/src/components/EditorHeader.tsx +296 -38
- package/src/components/ParamsProvider.tsx +7 -0
- package/src/components/RenderErrorBoundary.tsx +200 -0
- package/src/hooks/useParams.ts +5 -1
- package/src/hooks/useSyncHtmlThemeClass.ts +19 -0
- package/src/index.native.ts +7 -0
- package/src/index.ts +8 -0
- package/src/migrations/migratePipe.ts +59 -0
- package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +80 -0
- package/src/migrations/semver.ts +24 -0
- package/src/migrations/types.ts +9 -0
- package/src/mockOS/components/PermissionModal.tsx +3 -2
- package/src/mockOS/components/SubscriptionModal.tsx +400 -0
- package/src/mockOS/context/MockOSContext.tsx +61 -10
- package/src/mockOS/context/MockOSContextBase.ts +1 -0
- package/src/mockOS/hooks/useMockIap.ts +11 -0
- package/src/mockOS/index.ts +7 -0
- package/src/mockOS/managers/mockOSIapManager.ts +10 -0
- package/src/mockOS/managers/subscriptionManager.ts +36 -0
- package/src/modals/IconPickerModal.tsx +1 -1
- package/src/pages/ProjectDebug.tsx +331 -0
- package/src/pages/ProjectMigrationPage.tsx +92 -0
- package/src/pages/ProjectPage.tsx +313 -161
- package/src/pages/ProjectValidationPage.tsx +54 -0
- package/src/styles/base/_global.scss +58 -11
- package/src/styles/components/_attributes-editor.scss +1 -1
- package/src/styles/components/_bottom-bar.scss +7 -4
- package/src/styles/components/_editor-shell.scss +126 -4
- package/src/styles/components/_mockos-router.scss +3 -2
- package/src/styles/components/_ui-components.scss +10 -5
- package/src/styles/foundation/_colors.scss +78 -11
- package/src/styles/foundation/_mixins.scss +4 -1
- package/src/styles/foundation/_sizes.scss +4 -2
- package/src/styles/index.scss +1 -0
- package/src/styles/layout/_builder.scss +61 -0
- package/src/styles/layout/_project-validation.scss +214 -0
- package/src/styles/modals/_add-component.scss +4 -2
- package/src/styles/modals/_color-modal.scss +4 -2
- package/src/styles/modals/_modal-shell.scss +3 -1
- package/src/types/Device.ts +5 -0
- package/src/utils/__special_exceptions.ts +88 -0
- package/src/utils/analyseNode.ts +8 -2
- package/src/utils/analyseNodeByPatterns.ts +43 -9
- package/src/utils/extractTextStyle.ts +19 -6
- package/src/utils/extractViewStyle.ts +68 -59
- package/src/utils/getImage.ts +76 -0
- package/src/utils/novaToJson.ts +2 -1
- package/src/utils/pasteNode.ts +172 -0
- package/src/utils/patterns.ts +4 -3
- package/dist/android.svg +0 -43
- package/dist/apple.svg +0 -16
- package/dist/background.jpg +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// NOTE: Keep the `.js` extension so the compiled ESM output runs in Node.
|
|
4
|
+
import { runProjectMigrations } from '../src/migrations/migratePipe.js';
|
|
5
|
+
|
|
6
|
+
type JsonValue =
|
|
7
|
+
| null
|
|
8
|
+
| boolean
|
|
9
|
+
| number
|
|
10
|
+
| string
|
|
11
|
+
| JsonValue[]
|
|
12
|
+
| { [k: string]: JsonValue };
|
|
13
|
+
|
|
14
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
15
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJsonFile(filePath: string): unknown {
|
|
19
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeJsonFile(filePath: string, value: unknown): void {
|
|
24
|
+
const json = JSON.stringify(value, null, 2) + '\n';
|
|
25
|
+
fs.writeFileSync(filePath, json, 'utf8');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function main() {
|
|
29
|
+
// Resolve from current working directory (expected to be repo root when invoked).
|
|
30
|
+
const repoRoot = path.resolve(process.cwd());
|
|
31
|
+
|
|
32
|
+
const sampleDir = path.join(repoRoot, 'src', 'assets', 'samples');
|
|
33
|
+
const targets = [
|
|
34
|
+
'carousel-sample.json',
|
|
35
|
+
'paywall-1.json',
|
|
36
|
+
'simple-1.json',
|
|
37
|
+
'simple-2.json',
|
|
38
|
+
'unvalidated-crash1.json',
|
|
39
|
+
'unvalidated-crashcomponent1.json',
|
|
40
|
+
'unvalidated-builder1.json',
|
|
41
|
+
'vpn-onboard-1.json',
|
|
42
|
+
'vpn-onboard-2.json',
|
|
43
|
+
'vpn-onboard-3.json',
|
|
44
|
+
'vpn-onboard-4.json',
|
|
45
|
+
'vpn-onboard-5.json',
|
|
46
|
+
'vpn-onboard-6.json',
|
|
47
|
+
].map(f => path.join(sampleDir, f));
|
|
48
|
+
|
|
49
|
+
let changed = 0;
|
|
50
|
+
for (const filePath of targets) {
|
|
51
|
+
const before = readJsonFile(filePath);
|
|
52
|
+
if (!isPlainObject(before)) {
|
|
53
|
+
throw new Error(`Expected JSON object in ${filePath}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { project: after, applied } = runProjectMigrations(before as any);
|
|
57
|
+
|
|
58
|
+
const beforeStr = JSON.stringify(before);
|
|
59
|
+
const afterStr = JSON.stringify(after as unknown as JsonValue);
|
|
60
|
+
if (beforeStr !== afterStr) {
|
|
61
|
+
changed++;
|
|
62
|
+
writeJsonFile(filePath, after);
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.log(
|
|
65
|
+
`migrated ${path.relative(repoRoot, filePath)}: ${
|
|
66
|
+
applied.map(m => m.id).join(', ') || '(none)'
|
|
67
|
+
}`
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
// eslint-disable-next-line no-console
|
|
71
|
+
console.log(`up-to-date ${path.relative(repoRoot, filePath)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.log(`done. changed: ${changed}/${targets.length}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main();
|
|
@@ -78,7 +78,7 @@ export async function createGeneratedProps(
|
|
|
78
78
|
const fileName = `${componentName}Props.generated.ts`;
|
|
79
79
|
const filePath = path.join(componentDir, fileName);
|
|
80
80
|
|
|
81
|
-
const { pattern
|
|
81
|
+
const { pattern } = patternJson;
|
|
82
82
|
const attributes = pattern.attributes || {};
|
|
83
83
|
const allTypes = patternJson.types || {};
|
|
84
84
|
|
|
@@ -126,9 +126,8 @@ export async function createGeneratedProps(
|
|
|
126
126
|
return ` ${key}?: ${tsType};`;
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
: '';
|
|
129
|
+
// In schema v2 we always allow a nested `style` object (for CSSProperties-like overrides/defaults).
|
|
130
|
+
const styleLine = ` style?: Record<string, unknown>;`;
|
|
132
131
|
|
|
133
132
|
const childTsType =
|
|
134
133
|
typeof pattern.children === 'string' ? pattern.children : 'string';
|
|
@@ -155,8 +154,8 @@ export async function createGeneratedProps(
|
|
|
155
154
|
`export interface ${componentName}PropsGenerated {\n` +
|
|
156
155
|
` child: ${normalizedChildTsType};\n` +
|
|
157
156
|
` attributes: {\n` +
|
|
157
|
+
`${styleLine}\n` +
|
|
158
158
|
(attributeLines.length ? attributeLines.join('\n') + '\n' : '') +
|
|
159
|
-
indexSignature +
|
|
160
159
|
` };\n` +
|
|
161
160
|
`}\n` +
|
|
162
161
|
`\n` +
|
|
@@ -89,11 +89,6 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
89
89
|
`[${componentName}] pattern.json -> 'schemaVersion' must be a number`
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
|
-
if (typeof data.allowUnknownAttributes !== 'boolean') {
|
|
93
|
-
return fail(
|
|
94
|
-
`[${componentName}] pattern.json -> 'allowUnknownAttributes' must be a boolean`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
92
|
if (typeof data.pattern !== 'object' || data.pattern == null) {
|
|
98
93
|
return fail(
|
|
99
94
|
`[${componentName}] pattern.json -> 'pattern' must be an object`
|
|
@@ -110,12 +105,19 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
110
105
|
`[${componentName}] pattern.json -> 'pattern.type' must match the component folder name '${componentName}' (PascalCase).`
|
|
111
106
|
);
|
|
112
107
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
// children spec can only be: node | string | never
|
|
109
|
+
// - "node" means: Node | Node[]
|
|
110
|
+
// - "string" means: text node
|
|
111
|
+
// - "never" means: no children allowed
|
|
112
|
+
if (typeof pattern.children !== 'string') {
|
|
117
113
|
return fail(
|
|
118
|
-
`[${componentName}] pattern.json -> 'pattern.children' must be a string
|
|
114
|
+
`[${componentName}] pattern.json -> 'pattern.children' must be a string`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const allowedChildrenKinds = new Set(['node', 'string', 'never']);
|
|
118
|
+
if (!allowedChildrenKinds.has(pattern.children)) {
|
|
119
|
+
return fail(
|
|
120
|
+
`[${componentName}] pattern.json -> 'pattern.children' must be one of: node | string | never`
|
|
119
121
|
);
|
|
120
122
|
}
|
|
121
123
|
if (typeof pattern.attributes !== 'object' || pattern.attributes == null) {
|
|
@@ -298,25 +300,35 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
298
300
|
: parentData?.pattern?.children;
|
|
299
301
|
|
|
300
302
|
const mergedTypes = { ...(parentData.types || {}), ...(data.types || {}) };
|
|
303
|
+
const parentDefaults = buildDefaultsBlock(parentData);
|
|
304
|
+
const childDefaults = buildDefaultsBlock(data);
|
|
301
305
|
const mergedDefaults = {
|
|
302
|
-
...
|
|
303
|
-
...
|
|
306
|
+
...parentDefaults,
|
|
307
|
+
...childDefaults,
|
|
308
|
+
// Deep-merge `defaults.style` so parent style defaults aren't blown away.
|
|
309
|
+
style: {
|
|
310
|
+
...(isPlainObject(parentDefaults?.style) ? parentDefaults.style : {}),
|
|
311
|
+
...(isPlainObject(childDefaults?.style) ? childDefaults.style : {}),
|
|
312
|
+
},
|
|
304
313
|
};
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
: parentData.allowUnknownAttributes;
|
|
314
|
+
if (Object.keys(mergedDefaults.style || {}).length === 0) {
|
|
315
|
+
delete mergedDefaults.style;
|
|
316
|
+
}
|
|
309
317
|
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
318
|
+
const parentMetaStyles =
|
|
319
|
+
(parentData?.meta?.styles || parentData?.meta?.attributes || {}) ?? {};
|
|
320
|
+
const childMetaStyles =
|
|
321
|
+
(data?.meta?.styles || data?.meta?.attributes || {}) ?? {};
|
|
322
|
+
const mergedMetaStyles = {
|
|
323
|
+
...(isPlainObject(parentMetaStyles) ? parentMetaStyles : {}),
|
|
324
|
+
...(isPlainObject(childMetaStyles) ? childMetaStyles : {}),
|
|
313
325
|
};
|
|
314
326
|
const mergedMeta =
|
|
315
327
|
parentData.meta || data.meta
|
|
316
328
|
? {
|
|
317
329
|
...(parentData.meta || {}),
|
|
318
330
|
...(data.meta || {}),
|
|
319
|
-
|
|
331
|
+
styles: mergedMetaStyles,
|
|
320
332
|
}
|
|
321
333
|
: undefined;
|
|
322
334
|
|
|
@@ -332,7 +344,6 @@ async function validatePatternJson(componentDir, componentName) {
|
|
|
332
344
|
data = {
|
|
333
345
|
...parentData,
|
|
334
346
|
...data,
|
|
335
|
-
allowUnknownAttributes: mergedAllowUnknown,
|
|
336
347
|
types: mergedTypes,
|
|
337
348
|
defaults: mergedDefaults,
|
|
338
349
|
meta: mergedMeta,
|
|
@@ -30,11 +30,6 @@ export async function validatePatternJson(componentDir, componentName) {
|
|
|
30
30
|
`[${componentName}] pattern.json -> 'schemaVersion' must be a number`
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
|
-
if (typeof data.allowUnknownAttributes !== 'boolean') {
|
|
34
|
-
return fail(
|
|
35
|
-
`[${componentName}] pattern.json -> 'allowUnknownAttributes' must be a boolean`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
33
|
if (typeof data.pattern !== 'object' || data.pattern == null) {
|
|
39
34
|
return fail(
|
|
40
35
|
`[${componentName}] pattern.json -> 'pattern' must be an object`
|
|
@@ -46,12 +41,19 @@ export async function validatePatternJson(componentDir, componentName) {
|
|
|
46
41
|
`[${componentName}] pattern.json -> 'pattern.type' must be a string`
|
|
47
42
|
);
|
|
48
43
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
// children spec can only be: node | string | never
|
|
45
|
+
// - "node" means: Node | Node[]
|
|
46
|
+
// - "string" means: text node
|
|
47
|
+
// - "never" means: no children allowed
|
|
48
|
+
if (typeof pattern.children !== 'string') {
|
|
49
|
+
return fail(
|
|
50
|
+
`[${componentName}] pattern.json -> 'pattern.children' must be a string`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const allowedChildrenKinds = new Set(['node', 'string', 'never']);
|
|
54
|
+
if (!allowedChildrenKinds.has(pattern.children)) {
|
|
53
55
|
return fail(
|
|
54
|
-
`[${componentName}] pattern.json -> 'pattern.children' must be
|
|
56
|
+
`[${componentName}] pattern.json -> 'pattern.children' must be one of: node | string | never`
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
if (typeof pattern.attributes !== 'object' || pattern.attributes == null) {
|
package/src/.DS_Store
CHANGED
|
Binary file
|
package/src/AttributesEditor.tsx
CHANGED
|
@@ -31,6 +31,10 @@ type AttributesEditorProps = {
|
|
|
31
31
|
projectColors?: ProjectColors;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
35
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
type TabId = 'style' | 'container' | 'other';
|
|
35
39
|
|
|
36
40
|
type TabConfig = {
|
|
@@ -111,6 +115,23 @@ export function AttributesEditor({
|
|
|
111
115
|
const schema = getAttributeSchema(data?.type) ?? {};
|
|
112
116
|
const attributeMeta = getAttributeMeta(data?.type);
|
|
113
117
|
const attributes = (data?.attributes ?? {}) as NodeDefaultAttribute;
|
|
118
|
+
const styleAttributeKeys = useMemo(() => {
|
|
119
|
+
// schemaVersion=2 stores style-like props under attributes.style; keep legacy support for flat attrs.
|
|
120
|
+
const viewSchema = getAttributeSchema('View') ?? {};
|
|
121
|
+
const textSchema = getAttributeSchema('Text') ?? {};
|
|
122
|
+
return new Set([...Object.keys(viewSchema), ...Object.keys(textSchema)]);
|
|
123
|
+
}, []);
|
|
124
|
+
const getAttributeValue = useCallback(
|
|
125
|
+
(name: string) => {
|
|
126
|
+
const direct = (attributes as Record<string, unknown>)?.[name];
|
|
127
|
+
if (direct !== undefined && direct !== null) return direct;
|
|
128
|
+
const styleBag = (attributes as any)?.style as
|
|
129
|
+
| Record<string, unknown>
|
|
130
|
+
| undefined;
|
|
131
|
+
return styleBag?.[name];
|
|
132
|
+
},
|
|
133
|
+
[attributes],
|
|
134
|
+
);
|
|
114
135
|
const viewAttributes = useMemo<
|
|
115
136
|
Partial<ViewPropsGenerated['attributes']> | undefined
|
|
116
137
|
>(
|
|
@@ -304,16 +325,27 @@ export function AttributesEditor({
|
|
|
304
325
|
|
|
305
326
|
const handleAttributeChange = useCallback(
|
|
306
327
|
(name: string, val: unknown) => {
|
|
328
|
+
const prevAttrs =
|
|
329
|
+
((baseData?.attributes ?? {}) as Record<string, unknown>) ?? {};
|
|
330
|
+
const isStyleKey = styleAttributeKeys.has(name);
|
|
331
|
+
const nextAttrs: Record<string, unknown> = { ...prevAttrs };
|
|
332
|
+
if (isStyleKey) {
|
|
333
|
+
const prevStyle = (
|
|
334
|
+
isPlainObject(nextAttrs.style) ? nextAttrs.style : {}
|
|
335
|
+
) as Record<string, unknown>;
|
|
336
|
+
nextAttrs.style = { ...prevStyle, [name]: val };
|
|
337
|
+
// Normalize away legacy flat style keys once edited.
|
|
338
|
+
if (name in nextAttrs) delete nextAttrs[name];
|
|
339
|
+
} else {
|
|
340
|
+
nextAttrs[name] = val;
|
|
341
|
+
}
|
|
307
342
|
const next: NodeData<NodeDefaultAttribute> = {
|
|
308
343
|
...baseData,
|
|
309
|
-
attributes:
|
|
310
|
-
...((baseData?.attributes ?? {}) as NodeDefaultAttribute),
|
|
311
|
-
[name]: val,
|
|
312
|
-
},
|
|
344
|
+
attributes: nextAttrs as NodeDefaultAttribute,
|
|
313
345
|
};
|
|
314
346
|
onChange(next);
|
|
315
347
|
},
|
|
316
|
-
[baseData, onChange],
|
|
348
|
+
[baseData, onChange, styleAttributeKeys],
|
|
317
349
|
);
|
|
318
350
|
|
|
319
351
|
const renderIconTypeField = useCallback(
|
|
@@ -336,7 +368,7 @@ export function AttributesEditor({
|
|
|
336
368
|
borderRadius: 6,
|
|
337
369
|
border: '1px solid #ddd',
|
|
338
370
|
padding: '8px 10px',
|
|
339
|
-
background: '
|
|
371
|
+
background: 'hsl(var(--card, var(--rb-card, 0 0% 100%)))',
|
|
340
372
|
cursor: 'pointer',
|
|
341
373
|
}}
|
|
342
374
|
>
|
|
@@ -422,9 +454,7 @@ export function AttributesEditor({
|
|
|
422
454
|
const preferredScale = toPreferredScale(
|
|
423
455
|
attributeMeta?.[name]?.preferedScale,
|
|
424
456
|
);
|
|
425
|
-
const currentValue = (
|
|
426
|
-
name
|
|
427
|
-
];
|
|
457
|
+
const currentValue = getAttributeValue(name);
|
|
428
458
|
const isBoolean = isBooleanFieldType(type);
|
|
429
459
|
const wrapperClassNames = [
|
|
430
460
|
'attributes-editor__field-wrapper',
|
|
@@ -651,12 +681,12 @@ export function AttributesEditor({
|
|
|
651
681
|
<p className="attributes-editor__field-label">{label}</p>
|
|
652
682
|
) : null}
|
|
653
683
|
{type === 'iconType' ? (
|
|
654
|
-
renderIconTypeField(name,
|
|
684
|
+
renderIconTypeField(name, getAttributeValue(name))
|
|
655
685
|
) : (
|
|
656
686
|
<Field
|
|
657
687
|
name={name}
|
|
658
688
|
type={type}
|
|
659
|
-
value={
|
|
689
|
+
value={getAttributeValue(name)}
|
|
660
690
|
onChange={(val) => handleAttributeChange(name, val)}
|
|
661
691
|
componentType={data?.type}
|
|
662
692
|
projectColors={projectColorsForPicker}
|
package/src/RenderPage.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { RenderNode } from './build-components';
|
|
|
5
5
|
import { useRenderStore } from './store';
|
|
6
6
|
import { useLogRender } from './utils/useLogRender';
|
|
7
7
|
import { findNodeByKeyNested } from './utils/findNodeByKeyNested';
|
|
8
|
+
import type { NodeData } from './types/Node';
|
|
8
9
|
export type ScreenStyle = {
|
|
9
10
|
light: { backgroundColor: string; color: string; seperatorColor?: string };
|
|
10
11
|
dark: { backgroundColor: string; color: string; seperatorColor?: string };
|
|
@@ -15,6 +16,49 @@ interface RenderPageProps {
|
|
|
15
16
|
onSelectNode?: (node: Node | null) => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function isNodeDataLike(value: unknown): value is NodeData {
|
|
20
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const v = value as Record<string, unknown>;
|
|
24
|
+
return typeof v.type === 'string' && 'children' in v;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isNullish(value: unknown): value is null | undefined {
|
|
28
|
+
return value === null || value === undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isString(value: unknown): value is string {
|
|
32
|
+
return typeof value === 'string';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isArray(value: unknown): value is unknown[] {
|
|
36
|
+
return Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isEmptyPlainObject(value: unknown): boolean {
|
|
40
|
+
return (
|
|
41
|
+
typeof value === 'object' &&
|
|
42
|
+
value !== null &&
|
|
43
|
+
!Array.isArray(value) &&
|
|
44
|
+
Object.keys(value as object).length === 0
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isEmptyPreview(node: Node): boolean {
|
|
49
|
+
if (isNullish(node) || isEmptyPlainObject(node)) return true;
|
|
50
|
+
if (isString(node)) return node.trim().length === 0;
|
|
51
|
+
if (isArray(node)) return node.length === 0;
|
|
52
|
+
if (!isNodeDataLike(node)) return true;
|
|
53
|
+
|
|
54
|
+
const children = node.children as Node;
|
|
55
|
+
if (isNullish(children) || isEmptyPlainObject(children)) return true;
|
|
56
|
+
if (isString(children)) return children.trim().length === 0;
|
|
57
|
+
if (isArray(children)) return children.length === 0;
|
|
58
|
+
// children is another node object
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
18
62
|
export function RenderPage({ data, name, onSelectNode }: RenderPageProps) {
|
|
19
63
|
useLogRender('RenderPage');
|
|
20
64
|
const { previewMode, forceRender, setCurrent } = useRenderStore((s) => ({
|
|
@@ -23,6 +67,7 @@ export function RenderPage({ data, name, onSelectNode }: RenderPageProps) {
|
|
|
23
67
|
setCurrent: s.setCurrent,
|
|
24
68
|
}));
|
|
25
69
|
const previewRootRef = useRef<HTMLDivElement | null>(null);
|
|
70
|
+
const showEmptyState = isEmptyPreview(data);
|
|
26
71
|
|
|
27
72
|
useEffect(() => {
|
|
28
73
|
if (!previewMode) {
|
|
@@ -64,6 +109,16 @@ export function RenderPage({ data, name, onSelectNode }: RenderPageProps) {
|
|
|
64
109
|
<DeviceMockFrame appName={name}>
|
|
65
110
|
<div className="screen-preview" ref={previewRootRef}>
|
|
66
111
|
<RenderNode node={data} />
|
|
112
|
+
{showEmptyState && (
|
|
113
|
+
<div className="rb-empty-preview" aria-hidden="true">
|
|
114
|
+
<div className="rb-empty-preview__card">
|
|
115
|
+
<div className="rb-empty-preview__title">Empty screen</div>
|
|
116
|
+
<div className="rb-empty-preview__hint">
|
|
117
|
+
Add a component to start
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
67
122
|
</div>
|
|
68
123
|
</DeviceMockFrame>
|
|
69
124
|
);
|
|
Binary file
|