@developer_tribe/react-builder 1.0.2 → 1.0.4
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/AttributesEditor.d.ts +3 -1
- package/dist/RenderPage.d.ts +2 -1
- package/dist/android.svg +43 -0
- package/dist/apple.svg +16 -0
- package/dist/attributes-editor/Field.d.ts +4 -2
- package/dist/attributes-editor/SizeField.d.ts +9 -0
- package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
- package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +45 -0
- package/dist/build-components/Button/ButtonProps.generated.d.ts +8 -0
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +8 -0
- package/dist/build-components/Image/ImageProps.generated.d.ts +8 -0
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +8 -1
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +9 -3
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +9 -1
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +8 -1
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +8 -0
- package/dist/build-components/Text/TextProps.generated.d.ts +8 -0
- package/dist/build-components/View/ViewProps.generated.d.ts +8 -0
- package/dist/build-components/index.d.ts +2 -1
- package/dist/build-components/patterns.generated.d.ts +1612 -46
- package/dist/components/AttributesEditorPanel.d.ts +3 -4
- package/dist/components/Builder.d.ts +2 -1
- package/dist/components/BuilderButton.d.ts +9 -0
- package/dist/components/JsonTextEditor.d.ts +9 -0
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/modals/ColorModal.d.ts +3 -1
- package/dist/pages/ProjectPage.d.ts +3 -3
- package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
- package/dist/pages/tabs/SideTool.d.ts +8 -0
- package/dist/store.d.ts +9 -1
- package/dist/styles.css +1 -1
- package/dist/types/Project.d.ts +11 -0
- package/dist/utils/analyseNode.d.ts +1 -0
- package/dist/utils/extractImageStyle.d.ts +2 -1
- package/dist/utils/extractTextStyle.d.ts +8 -1
- package/dist/utils/extractViewStyle.d.ts +7 -1
- package/dist/utils/parseColor.d.ts +7 -0
- package/dist/utils/selection.d.ts +7 -0
- package/dist/utils/useMergedStyle.d.ts +2 -0
- package/package.json +2 -5
- package/src/.DS_Store +0 -0
- package/src/AttributesEditor.tsx +83 -16
- package/src/RenderPage.tsx +86 -4
- package/src/attributes-editor/Field.tsx +60 -165
- package/src/attributes-editor/SizeField.tsx +184 -0
- package/src/attributes-editor/SpecialCategorySection.tsx +12 -4
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +77 -0
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +61 -0
- package/src/build-components/BackgroundImage/pattern.json +45 -0
- package/src/build-components/Button/Button.tsx +29 -4
- package/src/build-components/Button/ButtonProps.generated.ts +8 -0
- package/src/build-components/Carousel/Carousel.tsx +25 -3
- package/src/build-components/Carousel/CarouselProps.generated.ts +8 -0
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +19 -4
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +8 -0
- package/src/build-components/CarouselDots/CarouselDots.tsx +13 -4
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +8 -0
- package/src/build-components/CarouselItem/CarouselItem.tsx +20 -4
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +8 -0
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +14 -3
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +8 -0
- package/src/build-components/Image/Image.tsx +27 -9
- package/src/build-components/Image/ImageProps.generated.ts +8 -0
- package/src/build-components/Image/pattern.json +1 -9
- package/src/build-components/Onboard/Onboard.tsx +2 -2
- package/src/build-components/Onboard/OnboardProps.generated.ts +8 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +11 -7
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +8 -1
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +17 -5
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +8 -0
- package/src/build-components/OnboardDot/OnboardDot.tsx +68 -39
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +9 -3
- package/src/build-components/OnboardDot/pattern.json +3 -19
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +37 -14
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +8 -0
- package/src/build-components/OnboardImage/OnboardImage.tsx +28 -6
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +9 -1
- package/src/build-components/OnboardItem/OnboardItem.tsx +15 -14
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +8 -0
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +35 -20
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +8 -1
- package/src/build-components/OnboardProvider/pattern.json +0 -8
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +8 -0
- package/src/build-components/OnboardSubtitle/pattern.json +1 -1
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +8 -0
- package/src/build-components/OnboardTitle/pattern.json +1 -1
- package/src/build-components/RenderNode.generated.tsx +3 -0
- package/src/build-components/Text/Text.tsx +28 -10
- package/src/build-components/Text/TextProps.generated.ts +8 -0
- package/src/build-components/View/View.tsx +25 -3
- package/src/build-components/View/ViewProps.generated.ts +8 -0
- package/src/build-components/View/pattern.json +67 -1
- package/src/build-components/index.ts +5 -0
- package/src/build-components/patterns.generated.ts +1620 -46
- package/src/components/AttributesEditorPanel.tsx +13 -6
- package/src/components/Builder.tsx +200 -56
- package/src/components/BuilderButton.tsx +127 -0
- package/src/components/DeviceNavigationBar.tsx +0 -1
- package/src/components/EditorHeader.tsx +11 -1
- package/src/components/JsonTextEditor.tsx +185 -0
- package/src/index.ts +2 -2
- package/src/mockOS/components/MockOSRouter.tsx +17 -3
- package/src/mockOS/context/MockOSContext.tsx +0 -5
- package/src/mockOS/managers/mockPermissionManager.ts +0 -4
- package/src/mockOS/managers/navigationManager.ts +1 -6
- package/src/modals/ColorModal.tsx +306 -71
- package/src/modals/LocalicationModal.tsx +4 -5
- package/src/modals/Modal.tsx +8 -1
- package/src/pages/ProjectPage.tsx +299 -55
- package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
- package/src/pages/tabs/SideTool.tsx +260 -0
- package/src/size-matters/index.ts +6 -0
- package/src/store.ts +18 -1
- package/src/styles/base/_global.scss +163 -7
- package/src/styles/components/_attributes-editor.scss +12 -0
- package/src/styles/components/_editor-shell.scss +25 -0
- package/src/styles/foundation/_sizes.scss +1 -1
- package/src/styles/layout/_builder.scss +66 -10
- package/src/styles/modals/_color-modal.scss +59 -1
- package/src/styles/utilities/_carousel.scss +9 -8
- package/src/types/Project.ts +14 -0
- package/src/utils/analyseNode.ts +98 -0
- package/src/utils/extractImageStyle.ts +3 -6
- package/src/utils/extractTextStyle.ts +19 -82
- package/src/utils/extractViewStyle.ts +41 -12
- package/src/utils/parseColor.ts +43 -0
- package/src/utils/selection.ts +24 -0
- package/src/utils/useMergedStyle.ts +16 -0
- package/dist/pages/tabs/BuilderTab.d.ts +0 -9
- package/dist/pages/tabs/DebugTab.d.ts +0 -7
- package/dist/pages/tabs/PreviewTab.d.ts +0 -3
- package/src/pages/tabs/DebugTab.tsx +0 -64
- package/src/pages/tabs/PreviewTab.tsx +0 -206
|
@@ -44,6 +44,35 @@
|
|
|
44
44
|
font-weight: 600;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
.color-modal__toggle {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: sizes.$spaceSnug;
|
|
51
|
+
padding: 0 sizes.$spaceCompact;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.color-modal__toggle-label {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: sizes.$spaceCompact;
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
color: colors.$textColor;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.color-modal__toggle-input {
|
|
65
|
+
width: 16px;
|
|
66
|
+
height: 16px;
|
|
67
|
+
accent-color: colors.$accentColor;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.color-modal__toggle-description {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
color: colors.$mutedTextColor;
|
|
74
|
+
}
|
|
75
|
+
|
|
47
76
|
.color-modal__link-button {
|
|
48
77
|
border: none;
|
|
49
78
|
background: none;
|
|
@@ -54,6 +83,24 @@
|
|
|
54
83
|
padding: 4px 8px;
|
|
55
84
|
}
|
|
56
85
|
|
|
86
|
+
.color-modal__add-color {
|
|
87
|
+
position: relative;
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.color-modal__add-color-input {
|
|
93
|
+
position: absolute;
|
|
94
|
+
inset: 0;
|
|
95
|
+
opacity: 0;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
width: 100%;
|
|
98
|
+
height: 100%;
|
|
99
|
+
border: 0;
|
|
100
|
+
padding: 0;
|
|
101
|
+
margin: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
57
104
|
.color-section {
|
|
58
105
|
display: flex;
|
|
59
106
|
flex-direction: column;
|
|
@@ -126,5 +173,16 @@
|
|
|
126
173
|
}
|
|
127
174
|
|
|
128
175
|
.color-modal__input {
|
|
129
|
-
|
|
176
|
+
// Keep the input in the DOM so browsers allow opening the native color picker
|
|
177
|
+
// via user gesture (programmatic click/showPicker), but visually hide it.
|
|
178
|
+
position: absolute;
|
|
179
|
+
width: 1px;
|
|
180
|
+
height: 1px;
|
|
181
|
+
padding: 0;
|
|
182
|
+
margin: -1px;
|
|
183
|
+
overflow: hidden;
|
|
184
|
+
clip: rect(0 0 0 0);
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
border: 0;
|
|
187
|
+
opacity: 0;
|
|
130
188
|
}
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
.embla__container {
|
|
13
13
|
display: flex;
|
|
14
14
|
touch-action: pan-y pinch-zoom;
|
|
15
|
-
margin-left: calc(var(--slide-spacing) * -1);
|
|
16
15
|
}
|
|
17
16
|
.embla__slide {
|
|
18
17
|
transform: translate3d(0, 0, 0);
|
|
@@ -84,6 +83,7 @@
|
|
|
84
83
|
-webkit-appearance: none;
|
|
85
84
|
appearance: none;
|
|
86
85
|
background-color: transparent;
|
|
86
|
+
--embla-dot-color: var(--detail-medium-contrast);
|
|
87
87
|
touch-action: manipulation;
|
|
88
88
|
display: inline-flex;
|
|
89
89
|
text-decoration: none;
|
|
@@ -91,23 +91,24 @@
|
|
|
91
91
|
border: 0;
|
|
92
92
|
padding: 0;
|
|
93
93
|
margin: 0;
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
/* Keep a reasonable hit-area, but allow bigger dots to grow it. */
|
|
95
|
+
width: max(2.6rem, calc(var(--embla-dot-size, 1.4rem) + 1.2rem));
|
|
96
|
+
height: max(2.6rem, calc(var(--embla-dot-size, 1.4rem) + 1.2rem));
|
|
96
97
|
display: flex;
|
|
97
98
|
align-items: center;
|
|
98
99
|
border-radius: 50%;
|
|
99
100
|
}
|
|
100
101
|
.embla__dot:after {
|
|
101
|
-
|
|
102
|
-
width: 1.4rem;
|
|
103
|
-
height: 1.4rem;
|
|
102
|
+
background-color: var(--embla-dot-color);
|
|
103
|
+
width: var(--embla-dot-size, 1.4rem);
|
|
104
|
+
height: var(--embla-dot-size, 1.4rem);
|
|
104
105
|
border-radius: 50%;
|
|
105
106
|
display: flex;
|
|
106
107
|
align-items: center;
|
|
107
108
|
content: '';
|
|
108
109
|
}
|
|
109
|
-
.embla__dot--selected
|
|
110
|
-
|
|
110
|
+
.embla__dot--selected {
|
|
111
|
+
--embla-dot-color: var(--text-body);
|
|
111
112
|
}
|
|
112
113
|
.carousel-provider {
|
|
113
114
|
height: 100%;
|
package/src/types/Project.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { Node } from '../types/Node';
|
|
2
2
|
import { AppConfig } from './PreviewConfig';
|
|
3
3
|
|
|
4
|
+
export type ProjectColorTokenMap = Record<string, string>;
|
|
5
|
+
|
|
6
|
+
export type ProjectThemeColors = {
|
|
7
|
+
light?: ProjectColorTokenMap;
|
|
8
|
+
dark?: ProjectColorTokenMap;
|
|
9
|
+
[themeName: string]: ProjectColorTokenMap | undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface ProjectColors {
|
|
13
|
+
STATIC_COLORS?: ProjectColorTokenMap;
|
|
14
|
+
THEME_COLORS?: ProjectThemeColors;
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export interface ProjectBase<T> {
|
|
5
18
|
name: string;
|
|
6
19
|
version: string;
|
|
7
20
|
data: T;
|
|
8
21
|
appConfig?: AppConfig;
|
|
22
|
+
projectColors?: ProjectColors;
|
|
9
23
|
}
|
|
10
24
|
|
|
11
25
|
export interface Project extends ProjectBase<Node> {}
|
package/src/utils/analyseNode.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Node, NodeData, NodeDefaultAttribute } from '../types/Node';
|
|
2
|
+
import { generateRandomKeyForNode } from './generateRandomKeyForNode';
|
|
2
3
|
|
|
3
4
|
export function isNodeNullOrUndefined<T = NodeDefaultAttribute>(
|
|
4
5
|
node: Node<T>,
|
|
@@ -17,6 +18,82 @@ export function isEmptyObject<T = NodeDefaultAttribute>(
|
|
|
17
18
|
return Object.keys(node as Object).length === 0;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function collectDuplicateKey(
|
|
22
|
+
node: Node<NodeDefaultAttribute>,
|
|
23
|
+
usedKeys: Set<string>,
|
|
24
|
+
): string | null {
|
|
25
|
+
if (isNodeNullOrUndefined(node) || isNodeString(node)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isNodeArray(node)) {
|
|
30
|
+
const nodeArray = node as unknown as Node<NodeDefaultAttribute>[];
|
|
31
|
+
for (const value of nodeArray) {
|
|
32
|
+
const duplicate = collectDuplicateKey(value, usedKeys);
|
|
33
|
+
if (duplicate) {
|
|
34
|
+
return duplicate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const recordData = node as NodeData<NodeDefaultAttribute>;
|
|
41
|
+
if (recordData.key) {
|
|
42
|
+
if (usedKeys.has(recordData.key)) {
|
|
43
|
+
return recordData.key;
|
|
44
|
+
}
|
|
45
|
+
usedKeys.add(recordData.key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (recordData.children) {
|
|
49
|
+
return collectDuplicateKey(
|
|
50
|
+
recordData.children as Node<NodeDefaultAttribute>,
|
|
51
|
+
usedKeys,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assignMissingKeys(
|
|
59
|
+
node: Node<NodeDefaultAttribute>,
|
|
60
|
+
usedKeys: Set<string>,
|
|
61
|
+
): Node<NodeDefaultAttribute> {
|
|
62
|
+
if (isNodeNullOrUndefined(node) || isNodeString(node)) {
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isNodeArray(node)) {
|
|
67
|
+
const nodeArray = node as unknown as Node<NodeDefaultAttribute>[];
|
|
68
|
+
return nodeArray.map((child) =>
|
|
69
|
+
assignMissingKeys(child, usedKeys),
|
|
70
|
+
) as unknown as Node<NodeDefaultAttribute>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const recordData = node as NodeData<NodeDefaultAttribute>;
|
|
74
|
+
|
|
75
|
+
let key = recordData.key;
|
|
76
|
+
if (!key) {
|
|
77
|
+
do {
|
|
78
|
+
key = generateRandomKeyForNode(recordData.type);
|
|
79
|
+
} while (usedKeys.has(key));
|
|
80
|
+
usedKeys.add(key);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const children = recordData.children
|
|
84
|
+
? (assignMissingKeys(
|
|
85
|
+
recordData.children as Node<NodeDefaultAttribute>,
|
|
86
|
+
usedKeys,
|
|
87
|
+
) as Node<NodeDefaultAttribute>)
|
|
88
|
+
: recordData.children;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...recordData,
|
|
92
|
+
key,
|
|
93
|
+
children,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
20
97
|
export function analyseNode(node: Node<NodeDefaultAttribute>): {
|
|
21
98
|
valid: boolean;
|
|
22
99
|
message?: string;
|
|
@@ -75,3 +152,24 @@ export function analyseNode(node: Node<NodeDefaultAttribute>): {
|
|
|
75
152
|
};
|
|
76
153
|
}
|
|
77
154
|
}
|
|
155
|
+
|
|
156
|
+
export function analyseAndProccess(
|
|
157
|
+
node: Node<NodeDefaultAttribute>,
|
|
158
|
+
): Node<NodeDefaultAttribute> | null {
|
|
159
|
+
if (isNodeNullOrUndefined(node)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { valid, message } = analyseNode(node);
|
|
164
|
+
if (!valid) {
|
|
165
|
+
throw new Error(message ?? 'Node is not valid');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const usedKeys = new Set<string>();
|
|
169
|
+
const duplicateKey = collectDuplicateKey(node, usedKeys);
|
|
170
|
+
if (duplicateKey) {
|
|
171
|
+
throw new Error(`Duplicate node key detected: ${duplicateKey}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return assignMissingKeys(node, usedKeys);
|
|
175
|
+
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import { ImagePropsGenerated } from '../build-components/Image/ImageProps.generated';
|
|
2
2
|
import type { NodeData } from '../types/Node';
|
|
3
|
+
import { extractViewStyle, ExtractViewStyleOptions } from './extractViewStyle';
|
|
3
4
|
|
|
4
5
|
export function extractImageStyle<T extends ImagePropsGenerated['attributes']>(
|
|
5
6
|
node: NodeData<T>,
|
|
7
|
+
options: ExtractViewStyleOptions = {},
|
|
6
8
|
) {
|
|
7
9
|
const attributes = node.attributes;
|
|
8
10
|
const style: React.CSSProperties = {};
|
|
9
11
|
|
|
10
12
|
if (!attributes) return style;
|
|
11
13
|
|
|
12
|
-
if (attributes.width !== undefined) style.width = attributes.width;
|
|
13
|
-
if (attributes.height !== undefined) style.height = attributes.height;
|
|
14
|
-
if (attributes.borderRadius !== undefined)
|
|
15
|
-
style.borderRadius = attributes.borderRadius;
|
|
16
|
-
|
|
17
14
|
// Map resizeMode to CSS object-fit
|
|
18
15
|
if (attributes.resizeMode === 'cover') style.objectFit = 'cover';
|
|
19
16
|
else if (attributes.resizeMode === 'contain') style.objectFit = 'contain';
|
|
20
17
|
else if (attributes.resizeMode === 'stretch') style.objectFit = 'fill';
|
|
21
18
|
else if (attributes.resizeMode === 'center') style.objectFit = 'none';
|
|
22
19
|
|
|
23
|
-
return style;
|
|
20
|
+
return { ...extractViewStyle(node, options), ...style };
|
|
24
21
|
}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type { NodeData } from '../types/Node';
|
|
2
2
|
import type { TextPropsGenerated } from '../build-components/Text/TextProps.generated';
|
|
3
|
+
import type { AppConfig } from '../types/PreviewConfig';
|
|
4
|
+
import { defaultAppConfig } from '../types/PreviewConfig';
|
|
5
|
+
import type { ProjectColors } from '../types/Project';
|
|
3
6
|
import { fs, parseSize } from '../size-matters';
|
|
4
|
-
import {
|
|
7
|
+
import { parseColor } from './parseColor';
|
|
8
|
+
import { extractViewStyle } from './extractViewStyle';
|
|
9
|
+
|
|
10
|
+
type ExtractTextStyleOptions = {
|
|
11
|
+
appConfig?: AppConfig;
|
|
12
|
+
projectColors?: ProjectColors;
|
|
13
|
+
};
|
|
5
14
|
|
|
6
15
|
export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
|
|
7
16
|
node: NodeData<T>,
|
|
17
|
+
options: ExtractTextStyleOptions = {},
|
|
8
18
|
) {
|
|
9
19
|
const attributes = node.attributes;
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
appConfig: { screenStyle, theme },
|
|
13
|
-
} = useRenderStore.getState();
|
|
20
|
+
const resolvedAppConfig = options.appConfig ?? defaultAppConfig;
|
|
21
|
+
const { screenStyle, theme } = resolvedAppConfig;
|
|
14
22
|
const fallbackColor =
|
|
15
23
|
theme === 'light' ? screenStyle.light.color : screenStyle.dark.color;
|
|
16
24
|
|
|
@@ -30,84 +38,13 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
|
|
|
30
38
|
style.fontSize = fs(14);
|
|
31
39
|
}
|
|
32
40
|
if (attributes.fontWeight) style.fontWeight = attributes.fontWeight;
|
|
33
|
-
|
|
41
|
+
const resolvedTextColor = parseColor(attributes.color, {
|
|
42
|
+
projectColors: options.projectColors,
|
|
43
|
+
appConfig: resolvedAppConfig,
|
|
44
|
+
});
|
|
45
|
+
style.color = resolvedTextColor ?? fallbackColor;
|
|
34
46
|
if (attributes.textAlign)
|
|
35
47
|
style.textAlign = attributes.textAlign as React.CSSProperties['textAlign'];
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
// Shorthand
|
|
39
|
-
if (attributes.padding !== undefined) style.padding = attributes.padding;
|
|
40
|
-
if (attributes.margin !== undefined) style.margin = attributes.margin;
|
|
41
|
-
|
|
42
|
-
// Axis shorthands
|
|
43
|
-
if (attributes.paddingHorizontal !== undefined) {
|
|
44
|
-
const v = parseSize(attributes.paddingHorizontal);
|
|
45
|
-
style.paddingLeft = v as React.CSSProperties['paddingLeft'];
|
|
46
|
-
style.paddingRight = v as React.CSSProperties['paddingRight'];
|
|
47
|
-
}
|
|
48
|
-
if (attributes.paddingVertical !== undefined) {
|
|
49
|
-
const v = parseSize(attributes.paddingVertical);
|
|
50
|
-
style.paddingTop = v as React.CSSProperties['paddingTop'];
|
|
51
|
-
style.paddingBottom = v as React.CSSProperties['paddingBottom'];
|
|
52
|
-
}
|
|
53
|
-
const marginHorizontalRaw = (attributes as Record<string, unknown>)
|
|
54
|
-
.marginHorizontal as string | number | undefined;
|
|
55
|
-
if (marginHorizontalRaw !== undefined) {
|
|
56
|
-
const v = parseSize(marginHorizontalRaw);
|
|
57
|
-
style.marginLeft = v as React.CSSProperties['marginLeft'];
|
|
58
|
-
style.marginRight = v as React.CSSProperties['marginRight'];
|
|
59
|
-
}
|
|
60
|
-
if (attributes.marginVertical !== undefined) {
|
|
61
|
-
const v = parseSize(attributes.marginVertical);
|
|
62
|
-
style.marginTop = v as React.CSSProperties['marginTop'];
|
|
63
|
-
style.marginBottom = v as React.CSSProperties['marginBottom'];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Edges
|
|
67
|
-
if (attributes.paddingTop !== undefined)
|
|
68
|
-
style.paddingTop = parseSize(
|
|
69
|
-
attributes.paddingTop,
|
|
70
|
-
) as React.CSSProperties['paddingTop'];
|
|
71
|
-
if (attributes.paddingBottom !== undefined)
|
|
72
|
-
style.paddingBottom = parseSize(
|
|
73
|
-
attributes.paddingBottom,
|
|
74
|
-
) as React.CSSProperties['paddingBottom'];
|
|
75
|
-
if (attributes.paddingLeft !== undefined)
|
|
76
|
-
style.paddingLeft = parseSize(
|
|
77
|
-
attributes.paddingLeft,
|
|
78
|
-
) as React.CSSProperties['paddingLeft'];
|
|
79
|
-
if (attributes.paddingRight !== undefined)
|
|
80
|
-
style.paddingRight = parseSize(
|
|
81
|
-
attributes.paddingRight,
|
|
82
|
-
) as React.CSSProperties['paddingRight'];
|
|
83
|
-
|
|
84
|
-
if (attributes.marginTop !== undefined)
|
|
85
|
-
style.marginTop = parseSize(
|
|
86
|
-
attributes.marginTop,
|
|
87
|
-
) as React.CSSProperties['marginTop'];
|
|
88
|
-
if (attributes.marginBottom !== undefined)
|
|
89
|
-
style.marginBottom = parseSize(
|
|
90
|
-
attributes.marginBottom,
|
|
91
|
-
) as React.CSSProperties['marginBottom'];
|
|
92
|
-
if (attributes.marginLeft !== undefined)
|
|
93
|
-
style.marginLeft = parseSize(
|
|
94
|
-
attributes.marginLeft,
|
|
95
|
-
) as React.CSSProperties['marginLeft'];
|
|
96
|
-
if (attributes.marginRight !== undefined)
|
|
97
|
-
style.marginRight = parseSize(
|
|
98
|
-
attributes.marginRight,
|
|
99
|
-
) as React.CSSProperties['marginRight'];
|
|
100
|
-
|
|
101
|
-
// Decor
|
|
102
|
-
if (attributes.backgroundColor)
|
|
103
|
-
style.backgroundColor = attributes.backgroundColor;
|
|
104
|
-
if (attributes.borderRadius !== undefined)
|
|
105
|
-
style.borderRadius =
|
|
106
|
-
attributes.borderRadius as React.CSSProperties['borderRadius'];
|
|
107
|
-
|
|
108
|
-
// Dimensions (rare for text but supported by schema)
|
|
109
|
-
if (attributes.width !== undefined) style.width = attributes.width;
|
|
110
|
-
if (attributes.height !== undefined) style.height = attributes.height;
|
|
111
|
-
|
|
112
|
-
return style;
|
|
49
|
+
return { ...extractViewStyle(node, options), ...style };
|
|
113
50
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
|
|
2
2
|
import type { NodeData } from '../types/Node';
|
|
3
|
+
import type { AppConfig } from '../types/PreviewConfig';
|
|
4
|
+
import type { ProjectColors } from '../types/Project';
|
|
3
5
|
import { parseSize } from '../size-matters';
|
|
6
|
+
import { parseColor } from './parseColor';
|
|
7
|
+
|
|
8
|
+
export type ExtractViewStyleOptions = {
|
|
9
|
+
appConfig?: AppConfig;
|
|
10
|
+
projectColors?: ProjectColors;
|
|
11
|
+
};
|
|
4
12
|
|
|
5
13
|
export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
6
14
|
node: NodeData<T>,
|
|
15
|
+
options: ExtractViewStyleOptions = {},
|
|
7
16
|
) {
|
|
8
17
|
const attributes = node.attributes;
|
|
9
18
|
const scrollable = attributes?.scrollable ?? false;
|
|
@@ -12,6 +21,10 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
12
21
|
flexDirection: 'column',
|
|
13
22
|
};
|
|
14
23
|
if (!attributes) return style;
|
|
24
|
+
const isEmptySizeValue = (value: unknown) =>
|
|
25
|
+
value === undefined ||
|
|
26
|
+
value === null ||
|
|
27
|
+
(typeof value === 'string' && value.trim() === '');
|
|
15
28
|
if (scrollable) {
|
|
16
29
|
if (attributes.flexDirection === 'row') {
|
|
17
30
|
style.overflowX = 'auto';
|
|
@@ -36,7 +49,7 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
36
49
|
property: K,
|
|
37
50
|
rawValue: string | number | undefined,
|
|
38
51
|
) => {
|
|
39
|
-
if (rawValue
|
|
52
|
+
if (isEmptySizeValue(rawValue)) return;
|
|
40
53
|
const parsed = parseSize(rawValue);
|
|
41
54
|
style[property] = parsed as React.CSSProperties[K];
|
|
42
55
|
};
|
|
@@ -45,44 +58,60 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
45
58
|
setParsedSize('padding', attributes.padding);
|
|
46
59
|
setParsedSize('margin', attributes.margin);
|
|
47
60
|
|
|
48
|
-
if (attributes.paddingHorizontal
|
|
61
|
+
if (!isEmptySizeValue(attributes.paddingHorizontal)) {
|
|
49
62
|
const parsed = parseSize(attributes.paddingHorizontal);
|
|
50
63
|
style.paddingLeft = parsed as React.CSSProperties['paddingLeft'];
|
|
51
64
|
style.paddingRight = parsed as React.CSSProperties['paddingRight'];
|
|
52
65
|
}
|
|
53
|
-
if (attributes.paddingVertical
|
|
66
|
+
if (!isEmptySizeValue(attributes.paddingVertical)) {
|
|
54
67
|
const parsed = parseSize(attributes.paddingVertical);
|
|
55
68
|
style.paddingTop = parsed as React.CSSProperties['paddingTop'];
|
|
56
69
|
style.paddingBottom = parsed as React.CSSProperties['paddingBottom'];
|
|
57
70
|
}
|
|
58
71
|
|
|
72
|
+
// Explicit per-side paddings should override paddingHorizontal/paddingVertical.
|
|
73
|
+
setParsedSize('paddingTop', attributes.paddingTop);
|
|
74
|
+
setParsedSize('paddingBottom', attributes.paddingBottom);
|
|
75
|
+
setParsedSize('paddingLeft', attributes.paddingLeft);
|
|
76
|
+
setParsedSize('paddingRight', attributes.paddingRight);
|
|
77
|
+
|
|
59
78
|
const marginHorizontalRaw = (attributes as Record<string, unknown>)
|
|
60
79
|
.marginHorizontal as string | number | undefined;
|
|
61
|
-
if (marginHorizontalRaw
|
|
80
|
+
if (!isEmptySizeValue(marginHorizontalRaw)) {
|
|
62
81
|
const parsed = parseSize(marginHorizontalRaw);
|
|
63
82
|
style.marginLeft = parsed as React.CSSProperties['marginLeft'];
|
|
64
83
|
style.marginRight = parsed as React.CSSProperties['marginRight'];
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
if (attributes.marginVertical
|
|
86
|
+
if (!isEmptySizeValue(attributes.marginVertical)) {
|
|
68
87
|
const parsed = parseSize(attributes.marginVertical);
|
|
69
88
|
style.marginTop = parsed as React.CSSProperties['marginTop'];
|
|
70
89
|
style.marginBottom = parsed as React.CSSProperties['marginBottom'];
|
|
71
90
|
}
|
|
72
91
|
|
|
73
|
-
setParsedSize('paddingTop', attributes.paddingTop);
|
|
74
|
-
setParsedSize('paddingBottom', attributes.paddingBottom);
|
|
75
|
-
setParsedSize('paddingLeft', attributes.paddingLeft);
|
|
76
|
-
setParsedSize('paddingRight', attributes.paddingRight);
|
|
77
92
|
setParsedSize('marginTop', attributes.marginTop);
|
|
78
93
|
setParsedSize('marginBottom', attributes.marginBottom);
|
|
79
94
|
setParsedSize('marginLeft', attributes.marginLeft);
|
|
80
95
|
setParsedSize('marginRight', attributes.marginRight);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
if (attributes.backgroundColor) {
|
|
97
|
+
style.backgroundColor =
|
|
98
|
+
parseColor(attributes.backgroundColor, {
|
|
99
|
+
projectColors: options.projectColors,
|
|
100
|
+
appConfig: options.appConfig,
|
|
101
|
+
}) ?? attributes.backgroundColor;
|
|
102
|
+
}
|
|
84
103
|
setParsedSize('borderRadius', attributes.borderRadius);
|
|
85
104
|
setParsedSize('width', attributes.width);
|
|
86
105
|
setParsedSize('height', attributes.height);
|
|
106
|
+
if (attributes.flex !== undefined)
|
|
107
|
+
style.flex = attributes.flex as React.CSSProperties['flex'];
|
|
108
|
+
if (attributes.position)
|
|
109
|
+
style.position = attributes.position as React.CSSProperties['position'];
|
|
110
|
+
setParsedSize('top', attributes.top);
|
|
111
|
+
setParsedSize('bottom', attributes.bottom);
|
|
112
|
+
setParsedSize('left', attributes.left);
|
|
113
|
+
setParsedSize('right', attributes.right);
|
|
114
|
+
if (attributes.zIndex !== undefined)
|
|
115
|
+
style.zIndex = attributes.zIndex as React.CSSProperties['zIndex'];
|
|
87
116
|
return style;
|
|
88
117
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AppConfig } from '../types/PreviewConfig';
|
|
2
|
+
import type { ProjectColors } from '../types/Project';
|
|
3
|
+
|
|
4
|
+
const STATIC_PREFIX = 'STATIC_COLORS.';
|
|
5
|
+
const THEME_PREFIX = 'THEME_COLORS.';
|
|
6
|
+
|
|
7
|
+
export type ParseColorOptions = {
|
|
8
|
+
projectColors?: ProjectColors;
|
|
9
|
+
appConfig?: AppConfig;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function parseColor(value?: string, options: ParseColorOptions = {}) {
|
|
13
|
+
if (typeof value !== 'string') return value;
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (!trimmed) return undefined;
|
|
16
|
+
|
|
17
|
+
const { projectColors, appConfig } = options;
|
|
18
|
+
if (!projectColors) return trimmed;
|
|
19
|
+
|
|
20
|
+
if (trimmed.startsWith(STATIC_PREFIX)) {
|
|
21
|
+
const token = trimmed.slice(STATIC_PREFIX.length);
|
|
22
|
+
const resolved = projectColors.STATIC_COLORS?.[token];
|
|
23
|
+
return typeof resolved === 'string' && resolved.trim()
|
|
24
|
+
? resolved.trim()
|
|
25
|
+
: trimmed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (trimmed.startsWith(THEME_PREFIX)) {
|
|
29
|
+
const token = trimmed.slice(THEME_PREFIX.length);
|
|
30
|
+
if (!token) return trimmed;
|
|
31
|
+
|
|
32
|
+
const theme = appConfig?.theme ?? 'light';
|
|
33
|
+
const themeTokens = projectColors.THEME_COLORS?.[theme];
|
|
34
|
+
const resolved = themeTokens?.[token];
|
|
35
|
+
if (typeof resolved === 'string' && resolved.trim()) {
|
|
36
|
+
return resolved.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
export const SELECTED_OUTLINE_STYLE: CSSProperties = {
|
|
4
|
+
outline: '2px solid #2684FF',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function getNodeKey(value: unknown): string | undefined {
|
|
8
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
9
|
+
if (!('key' in value)) return undefined;
|
|
10
|
+
const key = (value as { key?: unknown }).key;
|
|
11
|
+
return typeof key === 'string' ? key : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isNodeSelected(args: {
|
|
15
|
+
previewMode: boolean;
|
|
16
|
+
current?: unknown;
|
|
17
|
+
node?: unknown;
|
|
18
|
+
}): boolean {
|
|
19
|
+
const currentKey = getNodeKey(args.current);
|
|
20
|
+
const nodeKey = getNodeKey(args.node);
|
|
21
|
+
return (
|
|
22
|
+
!!args.previewMode && !!currentKey && !!nodeKey && currentKey === nodeKey
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
const EMPTY_STYLE: CSSProperties = {};
|
|
5
|
+
|
|
6
|
+
export function useMergedStyle(
|
|
7
|
+
base?: CSSProperties,
|
|
8
|
+
override?: CSSProperties,
|
|
9
|
+
): CSSProperties {
|
|
10
|
+
return useMemo(() => {
|
|
11
|
+
if (!base && !override) return EMPTY_STYLE;
|
|
12
|
+
if (!override) return base ?? EMPTY_STYLE;
|
|
13
|
+
if (!base) return override;
|
|
14
|
+
return { ...base, ...override };
|
|
15
|
+
}, [base, override]);
|
|
16
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Node } from '../..';
|
|
2
|
-
type BuilderTabProps = {
|
|
3
|
-
data: Node;
|
|
4
|
-
setData: (data: Node) => void;
|
|
5
|
-
current: Node;
|
|
6
|
-
setCurrent: (current: Node) => void;
|
|
7
|
-
};
|
|
8
|
-
export declare function BuilderTab({ data, setData, current, setCurrent, }: BuilderTabProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export {};
|