@developer_tribe/react-builder 1.2.19 → 1.2.21
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/web/useExtractTextStyle.d.ts +1 -1
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +2 -0
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +2 -0
- package/dist/build-components/Button/ButtonProps.generated.d.ts +2 -0
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +2 -0
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +2 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +2 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +2 -0
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +2 -0
- package/dist/build-components/CountDown/CountDownProps.generated.d.ts +2 -0
- package/dist/build-components/Counter/CounterProps.generated.d.ts +2 -0
- package/dist/build-components/Image/ImageProps.generated.d.ts +2 -0
- package/dist/build-components/Main/MainProps.generated.d.ts +2 -0
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +4 -1
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +2 -0
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallCounter/PaywallCounterProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +2 -0
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +2 -0
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +2 -0
- package/dist/build-components/Text/TextProps.generated.d.ts +2 -0
- package/dist/build-components/View/View.d.ts +1 -1
- package/dist/build-components/View/ViewProps.generated.d.ts +2 -0
- package/dist/build-components/patterns.generated.d.ts +310 -10
- package/dist/components/BuilderButton.d.ts +3 -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 +2 -0
- package/dist/styles.css +1 -1
- package/dist/utils/extractTextStyle/extractTextStyle.d.ts +1 -0
- package/package.json +1 -1
- package/scripts/migrate-patterns-to-v2.mjs +13 -8
- package/scripts/prebuild/icon-generator.js +34 -37
- package/src/assets/loading_animation.json +2587 -1
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/carousel-sample.json +281 -199
- package/src/assets/samples/getSamples.ts +16 -1
- package/src/assets/samples/paywall-1.json +93 -77
- package/src/assets/samples/paywall-2.json +77 -77
- package/src/assets/samples/paywall-app-delete-offer.json +353 -0
- package/src/assets/samples/paywall-app-open-offer.json +353 -0
- package/src/assets/samples/paywall-back-offer.json +353 -0
- package/src/assets/samples/paywall-notification-offer.json +353 -0
- package/src/assets/samples/simple-1.json +13 -13
- package/src/assets/samples/simple-2.json +97 -97
- package/src/assets/samples/unmigrated-builder-1.1.1.json +25 -25
- package/src/assets/samples/unmigrated-builder1.json +1 -1
- package/src/assets/samples/unvalidated-builder1.json +15 -15
- package/src/assets/samples/unvalidated-crash1.json +4 -4
- package/src/assets/samples/vpn-onboard-1.json +122 -89
- package/src/assets/samples/vpn-onboard-2.json +119 -86
- package/src/assets/samples/vpn-onboard-3.json +125 -90
- package/src/assets/samples/vpn-onboard-4.json +125 -90
- package/src/assets/samples/vpn-onboard-5.json +161 -119
- package/src/assets/samples/vpn-onboard-6.json +122 -92
- package/src/attribute-analyser/style/web/useExtractTextStyle.ts +9 -2
- package/src/build-components/BIcon/BIconProps.generated.ts +2 -0
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +2 -0
- package/src/build-components/Button/ButtonProps.generated.ts +2 -0
- package/src/build-components/Carousel/CarouselProps.generated.ts +2 -0
- package/src/build-components/Carousel/pattern.json +2 -8
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +2 -0
- package/src/build-components/CarouselButtons/pattern.json +2 -9
- package/src/build-components/CarouselDots/CarouselDots.tsx +112 -12
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +2 -0
- package/src/build-components/CarouselDots/pattern.json +1 -3
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +2 -0
- package/src/build-components/CarouselItem/pattern.json +1 -3
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +5 -44
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +2 -0
- package/src/build-components/CarouselProvider/pattern.json +6 -0
- package/src/build-components/CountDown/CountDownProps.generated.ts +2 -0
- package/src/build-components/CountDown/pattern.json +0 -1
- package/src/build-components/Counter/CounterProps.generated.ts +2 -0
- package/src/build-components/Counter/pattern.json +0 -1
- package/src/build-components/Image/Image.tsx +1 -1
- package/src/build-components/Image/ImageProps.generated.ts +2 -0
- package/src/build-components/Main/MainProps.generated.ts +2 -0
- package/src/build-components/Main/pattern.json +1 -3
- package/src/build-components/Onboard/OnboardProps.generated.ts +2 -0
- package/src/build-components/Onboard/pattern.json +2 -6
- package/src/build-components/OnboardButton/OnboardButton.tsx +0 -4
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +2 -0
- package/src/build-components/OnboardButton/pattern.json +9 -14
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +17 -20
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +2 -0
- package/src/build-components/OnboardButtons/pattern.json +15 -15
- package/src/build-components/OnboardDot/OnboardDot.tsx +73 -42
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +4 -1
- package/src/build-components/OnboardDot/pattern.json +28 -10
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +63 -51
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +2 -0
- package/src/build-components/OnboardFooter/pattern.json +6 -3
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +2 -0
- package/src/build-components/OnboardImage/pattern.json +1 -5
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +2 -0
- package/src/build-components/OnboardItem/pattern.json +3 -11
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +2 -0
- package/src/build-components/OnboardProvider/pattern.json +2 -8
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +2 -0
- package/src/build-components/OnboardSubtitle/pattern.json +1 -4
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +2 -0
- package/src/build-components/OnboardTitle/pattern.json +1 -4
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +2 -0
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +2 -0
- package/src/build-components/PaywallCloseButton/pattern.json +1 -3
- package/src/build-components/PaywallCounter/PaywallCounterProps.generated.ts +2 -0
- package/src/build-components/PaywallCounter/pattern.json +0 -1
- package/src/build-components/PaywallOptions/PaywallOptions.tsx +1 -1
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +2 -0
- package/src/build-components/PaywallOptions/pattern.json +1 -3
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +2 -0
- package/src/build-components/PaywallProvider/pattern.json +1 -3
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +2 -0
- package/src/build-components/PaywallSubscribeButton/pattern.json +1 -3
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +2 -0
- package/src/build-components/RadioButton/pattern.json +1 -3
- package/src/build-components/RenderNode.generated.tsx +1 -1
- package/src/build-components/Text/TextProps.generated.ts +2 -0
- package/src/build-components/View/View.tsx +11 -7
- package/src/build-components/View/ViewProps.generated.ts +2 -0
- package/src/build-components/View/pattern.json +8 -0
- package/src/build-components/patterns.generated.ts +300 -10
- package/src/build-components/useNode.ts +20 -4
- package/src/components/Builder.tsx +98 -8
- package/src/components/BuilderButton.tsx +39 -7
- package/src/components/DeviceButton.tsx +5 -1
- package/src/pages/DebugJsonPage.tsx +109 -1
- package/src/pages/ProjectDebug.tsx +0 -1
- package/src/pages/ProjectPage.tsx +2 -2
- package/src/store.ts +8 -0
- package/src/styles/base/_global.scss +0 -5
- package/src/styles/components/_editor-shell.scss +18 -3
- package/src/styles/components/_onboard.scss +0 -17
- package/src/styles/foundation/_colors.scss +1 -4
- package/src/styles/foundation/_typography.scss +0 -1
- package/src/styles/layout/_builder.scss +20 -0
- package/src/styles/modals/_product-presets-modal.scss +0 -2
- package/src/styles/utilities/_carousel.scss +0 -32
- package/src/utils/analyseNodeByPatterns.ts +16 -6
- package/src/utils/extractTextStyle/extractTextStyle.ts +47 -13
- package/src/utils/extractViewStyle/extractViewStyle.ts +118 -39
- package/src/utils/logRenderStore.ts +7 -9
- package/src/utils/logger.ts +1 -5
- package/src/utils/novaToJson.ts +7 -3
- package/src/utils/repairNodeKeys.ts +1 -4
|
@@ -9,18 +9,33 @@ export default function useNode<
|
|
|
9
9
|
if (!defaults) return node;
|
|
10
10
|
const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T & {
|
|
11
11
|
style?: Record<string, unknown>;
|
|
12
|
+
styles?: Record<string, unknown>;
|
|
12
13
|
};
|
|
13
14
|
const defaultAttributes = defaults as T as T & {
|
|
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)
|
|
19
|
+
const defaultStyle = {
|
|
20
|
+
...(defaultAttributes?.styles ?? {}),
|
|
21
|
+
...(defaultAttributes?.style ?? {}),
|
|
22
|
+
};
|
|
23
|
+
// Merge node style from both node.attributes.style and node.attributes.styles (preferring styles)
|
|
24
|
+
const nodeStyle = {
|
|
25
|
+
...(nodeAttributes?.style ?? {}),
|
|
26
|
+
...(nodeAttributes?.styles ?? {}),
|
|
27
|
+
};
|
|
28
|
+
const mergedStyle = {
|
|
29
|
+
...defaultStyle,
|
|
30
|
+
...nodeStyle,
|
|
15
31
|
};
|
|
16
32
|
const mergedAttributes: T = {
|
|
17
33
|
...(defaultAttributes as T),
|
|
18
34
|
...(nodeAttributes as T),
|
|
19
35
|
// Deep merge `style` so default style values aren't lost when the node provides partial style overrides.
|
|
20
|
-
style
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
36
|
+
// Keep both `style` (for runtime back-compat) and `styles` (for editor schemaVersion=2) in sync.
|
|
37
|
+
style: mergedStyle,
|
|
38
|
+
styles: mergedStyle,
|
|
24
39
|
} as T;
|
|
25
40
|
if (
|
|
26
41
|
mergedAttributes &&
|
|
@@ -29,6 +44,7 @@ export default function useNode<
|
|
|
29
44
|
Object.keys((mergedAttributes as any).style).length === 0
|
|
30
45
|
) {
|
|
31
46
|
delete (mergedAttributes as any).style;
|
|
47
|
+
delete (mergedAttributes as any).styles;
|
|
32
48
|
}
|
|
33
49
|
return { ...node, attributes: mergedAttributes };
|
|
34
50
|
}
|
|
@@ -13,6 +13,7 @@ import { AddComponentModal } from '../modals/AddComponentModal';
|
|
|
13
13
|
import { BuilderButton } from './BuilderButton';
|
|
14
14
|
import { generateRandomKeyForNode } from '../utils/generateRandomKeyForNode';
|
|
15
15
|
import { collectNodeKeys } from '../utils/repairNodeKeys';
|
|
16
|
+
import { useRenderStore } from '../store';
|
|
16
17
|
|
|
17
18
|
type BuilderEditorProps = {
|
|
18
19
|
data: Node;
|
|
@@ -30,6 +31,68 @@ interface BuilderEditorComponentProps {
|
|
|
30
31
|
onReorder?: (prev: Node[], next: Node[]) => void;
|
|
31
32
|
onMoveChildUp?: (parent: Node, childIndex: number) => void;
|
|
32
33
|
onMoveChildDown?: (parent: Node, childIndex: number) => void;
|
|
34
|
+
maxNestedDepth?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type BuilderTreeNodeProps = {
|
|
38
|
+
node: Node;
|
|
39
|
+
depthRemaining: number;
|
|
40
|
+
onClick: (node: Node) => void;
|
|
41
|
+
onDelete?: (node: Node) => void;
|
|
42
|
+
onMoveUp?: () => void;
|
|
43
|
+
onMoveDown?: () => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function BuilderTreeNode({
|
|
47
|
+
node,
|
|
48
|
+
depthRemaining,
|
|
49
|
+
onClick,
|
|
50
|
+
onDelete,
|
|
51
|
+
onMoveUp,
|
|
52
|
+
onMoveDown,
|
|
53
|
+
}: BuilderTreeNodeProps) {
|
|
54
|
+
const children = getNodeChildren(node);
|
|
55
|
+
const hasChildren = children.length > 0;
|
|
56
|
+
const childrenCount =
|
|
57
|
+
hasChildren && depthRemaining <= 0 ? children.length : undefined;
|
|
58
|
+
const onMore =
|
|
59
|
+
hasChildren && depthRemaining <= 0 ? () => onClick(node) : undefined;
|
|
60
|
+
const button = (
|
|
61
|
+
<BuilderButton
|
|
62
|
+
onClick={() => {
|
|
63
|
+
onClick(node);
|
|
64
|
+
}}
|
|
65
|
+
node={node}
|
|
66
|
+
onDelete={onDelete}
|
|
67
|
+
onMoveUp={onMoveUp}
|
|
68
|
+
onMoveDown={onMoveDown}
|
|
69
|
+
childrenCount={childrenCount}
|
|
70
|
+
onMore={onMore}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!hasChildren) {
|
|
75
|
+
return button;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (depthRemaining <= 0) {
|
|
79
|
+
return <div className="builder__children">{button}</div>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="builder__children">
|
|
84
|
+
{button}
|
|
85
|
+
{children.map((child, index) => (
|
|
86
|
+
<BuilderTreeNode
|
|
87
|
+
key={index}
|
|
88
|
+
node={child}
|
|
89
|
+
depthRemaining={depthRemaining - 1}
|
|
90
|
+
onClick={onClick}
|
|
91
|
+
onDelete={onDelete}
|
|
92
|
+
/>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
33
96
|
}
|
|
34
97
|
|
|
35
98
|
function BuilderComponent({
|
|
@@ -40,6 +103,7 @@ function BuilderComponent({
|
|
|
40
103
|
onReorder,
|
|
41
104
|
onMoveChildUp,
|
|
42
105
|
onMoveChildDown,
|
|
106
|
+
maxNestedDepth,
|
|
43
107
|
}: BuilderEditorComponentProps) {
|
|
44
108
|
if (isNodeString(node)) {
|
|
45
109
|
return (
|
|
@@ -64,6 +128,12 @@ function BuilderComponent({
|
|
|
64
128
|
|
|
65
129
|
if (isNodeArray(node)) {
|
|
66
130
|
const list = node as Node[];
|
|
131
|
+
const depthRemaining = Math.max(
|
|
132
|
+
0,
|
|
133
|
+
Number.isFinite(maxNestedDepth) && (maxNestedDepth ?? 0) > 0
|
|
134
|
+
? Math.floor(maxNestedDepth ?? 1) - 1
|
|
135
|
+
: 4,
|
|
136
|
+
);
|
|
67
137
|
|
|
68
138
|
const moveItem = (index: number, direction: -1 | 1) => {
|
|
69
139
|
if (!onReorder) return;
|
|
@@ -79,11 +149,10 @@ function BuilderComponent({
|
|
|
79
149
|
<div className="builder__list">
|
|
80
150
|
{list.map((item, index) => (
|
|
81
151
|
<div key={index} className="builder__list-item">
|
|
82
|
-
<
|
|
83
|
-
onClick={() => {
|
|
84
|
-
onClick(item);
|
|
85
|
-
}}
|
|
152
|
+
<BuilderTreeNode
|
|
86
153
|
node={item}
|
|
154
|
+
depthRemaining={depthRemaining}
|
|
155
|
+
onClick={onClick}
|
|
87
156
|
onDelete={onDelete}
|
|
88
157
|
onMoveUp={
|
|
89
158
|
onReorder && index > 0 ? () => moveItem(index, -1) : undefined
|
|
@@ -109,6 +178,12 @@ function BuilderComponent({
|
|
|
109
178
|
? (rawChildren as Node[])
|
|
110
179
|
: [rawChildren]
|
|
111
180
|
: null;
|
|
181
|
+
const depthRemaining = Math.max(
|
|
182
|
+
0,
|
|
183
|
+
Number.isFinite(maxNestedDepth) && (maxNestedDepth ?? 0) > 0
|
|
184
|
+
? Math.floor(maxNestedDepth ?? 1) - 1
|
|
185
|
+
: 4,
|
|
186
|
+
);
|
|
112
187
|
|
|
113
188
|
return (
|
|
114
189
|
<div className="builder__node">
|
|
@@ -122,12 +197,11 @@ function BuilderComponent({
|
|
|
122
197
|
/>
|
|
123
198
|
{children &&
|
|
124
199
|
children.map((child, index) => (
|
|
125
|
-
<
|
|
126
|
-
onClick={() => {
|
|
127
|
-
onClick(child);
|
|
128
|
-
}}
|
|
200
|
+
<BuilderTreeNode
|
|
129
201
|
key={index}
|
|
130
202
|
node={child}
|
|
203
|
+
depthRemaining={depthRemaining}
|
|
204
|
+
onClick={onClick}
|
|
131
205
|
onDelete={onDelete}
|
|
132
206
|
onMoveUp={
|
|
133
207
|
onMoveChildUp && hasArrayChildren && index > 0
|
|
@@ -158,7 +232,12 @@ export function Builder({
|
|
|
158
232
|
}: BuilderEditorProps) {
|
|
159
233
|
useLogRender('Builder');
|
|
160
234
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
|
235
|
+
const listMaxNested = useRenderStore((state) => state.listMaxNested);
|
|
161
236
|
const usedKeys = useMemo(() => collectNodeKeys(data), [data]);
|
|
237
|
+
const resolvedListMaxNested =
|
|
238
|
+
Number.isFinite(listMaxNested) && listMaxNested > 0
|
|
239
|
+
? Math.floor(listMaxNested)
|
|
240
|
+
: 5;
|
|
162
241
|
const breadcrumbPath = useMemo(() => {
|
|
163
242
|
const path = findNodePath(data, current);
|
|
164
243
|
if (path.length) return path;
|
|
@@ -482,6 +561,7 @@ export function Builder({
|
|
|
482
561
|
onReorder={handleReorder}
|
|
483
562
|
onMoveChildUp={handleMoveChildUp}
|
|
484
563
|
onMoveChildDown={handleMoveChildDown}
|
|
564
|
+
maxNestedDepth={resolvedListMaxNested}
|
|
485
565
|
node={current}
|
|
486
566
|
/>
|
|
487
567
|
{isAddModalOpen && (
|
|
@@ -510,6 +590,16 @@ function appendChild(children: Node, childToAppend: Node): Node {
|
|
|
510
590
|
return [children as Node, childToAppend];
|
|
511
591
|
}
|
|
512
592
|
|
|
593
|
+
function getNodeChildren(node: Node): Node[] {
|
|
594
|
+
if (isNodeNullOrUndefined(node) || isNodeString(node) || isNodeArray(node)) {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
const nodeData = node as NodeData<NodeDefaultAttribute>;
|
|
598
|
+
const children = nodeData.children;
|
|
599
|
+
if (!children) return [];
|
|
600
|
+
return Array.isArray(children) ? children : [children];
|
|
601
|
+
}
|
|
602
|
+
|
|
513
603
|
function getNodeLabel(node: Node): string {
|
|
514
604
|
if (isNodeNullOrUndefined(node)) return 'Empty';
|
|
515
605
|
if (isNodeString(node)) return node as string;
|
|
@@ -10,6 +10,8 @@ export type BuilderButtonProps = {
|
|
|
10
10
|
onDelete?: (node: Node) => void;
|
|
11
11
|
onMoveUp?: () => void;
|
|
12
12
|
onMoveDown?: () => void;
|
|
13
|
+
childrenCount?: number;
|
|
14
|
+
onMore?: () => void;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export function BuilderButton({
|
|
@@ -18,6 +20,8 @@ export function BuilderButton({
|
|
|
18
20
|
onDelete,
|
|
19
21
|
onMoveUp,
|
|
20
22
|
onMoveDown,
|
|
23
|
+
childrenCount,
|
|
24
|
+
onMore,
|
|
21
25
|
}: BuilderButtonProps) {
|
|
22
26
|
// IMPORTANT: Hooks must be called unconditionally on every render.
|
|
23
27
|
// (Early returns before hooks can trigger React internal invariants.)
|
|
@@ -83,7 +87,8 @@ export function BuilderButton({
|
|
|
83
87
|
: '';
|
|
84
88
|
const hasTitle = title.length > 0;
|
|
85
89
|
const topLabel = hasTitle ? title : baseLabel;
|
|
86
|
-
const shouldShowBottom =
|
|
90
|
+
const shouldShowBottom =
|
|
91
|
+
hasTitle || conditionLabel.length > 0 || typeof childrenCount === 'number';
|
|
87
92
|
|
|
88
93
|
return (
|
|
89
94
|
<div className="builder__button">
|
|
@@ -140,6 +145,22 @@ export function BuilderButton({
|
|
|
140
145
|
role="menu"
|
|
141
146
|
aria-label="Node actions"
|
|
142
147
|
>
|
|
148
|
+
{onMore ? (
|
|
149
|
+
<li role="none">
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
className="builder__button-actions-item"
|
|
153
|
+
role="menuitem"
|
|
154
|
+
onClick={(event) => {
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
onMore();
|
|
157
|
+
setIsMenuOpen(false);
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
More
|
|
161
|
+
</button>
|
|
162
|
+
</li>
|
|
163
|
+
) : null}
|
|
143
164
|
<li role="none">
|
|
144
165
|
<button
|
|
145
166
|
type="button"
|
|
@@ -163,14 +184,25 @@ export function BuilderButton({
|
|
|
163
184
|
</div>
|
|
164
185
|
{shouldShowBottom && (
|
|
165
186
|
<span className="builder__button-condition">
|
|
166
|
-
{hasTitle ? (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
{hasTitle || conditionLabel.length > 0 ? (
|
|
188
|
+
<span className="builder__button-condition-text">
|
|
189
|
+
{hasTitle ? (
|
|
190
|
+
<>
|
|
191
|
+
<small>{baseLabel}</small>
|
|
192
|
+
{conditionLabel ? ` · ${conditionLabel}` : ''}
|
|
193
|
+
</>
|
|
194
|
+
) : (
|
|
195
|
+
conditionLabel
|
|
196
|
+
)}
|
|
197
|
+
</span>
|
|
171
198
|
) : (
|
|
172
|
-
|
|
199
|
+
<span className="builder__button-condition-text" />
|
|
173
200
|
)}
|
|
201
|
+
{typeof childrenCount === 'number' ? (
|
|
202
|
+
<span className="builder__button-children-count">
|
|
203
|
+
{childrenCount} children
|
|
204
|
+
</span>
|
|
205
|
+
) : null}
|
|
174
206
|
</span>
|
|
175
207
|
)}
|
|
176
208
|
</div>
|
|
@@ -38,7 +38,11 @@ export function DeviceButton({
|
|
|
38
38
|
>
|
|
39
39
|
{device.name} <br />
|
|
40
40
|
{device.width}×{device.height} ({aspect})
|
|
41
|
-
{platformIcon &&
|
|
41
|
+
{platformIcon && (
|
|
42
|
+
<span>
|
|
43
|
+
<img src={platformIcon} alt="" aria-hidden="true" />
|
|
44
|
+
</span>
|
|
45
|
+
)}
|
|
42
46
|
</button>
|
|
43
47
|
);
|
|
44
48
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import type { Node } from '../types/Node';
|
|
3
|
+
import type { NodeData, NodeDefaultAttribute } from '../types/Node';
|
|
3
4
|
import type { AppConfig } from '../types/PreviewConfig';
|
|
4
5
|
import { Checkbox } from '../components/Checkbox';
|
|
5
6
|
import { JsonTextEditor } from '../components/JsonTextEditor';
|
|
6
7
|
import { analyseAndProccess } from '../utils/analyseNode';
|
|
7
8
|
import { logRenderStore } from '../utils/logRenderStore';
|
|
8
9
|
import { useRenderStore } from '../store';
|
|
10
|
+
import {
|
|
11
|
+
isNodeArray,
|
|
12
|
+
isNodeNullOrUndefined,
|
|
13
|
+
isNodeString,
|
|
14
|
+
} from '../utils/nodeGuards';
|
|
9
15
|
|
|
10
16
|
export type DebugJsonPageProps = {
|
|
11
17
|
data: Node | null | undefined;
|
|
@@ -38,6 +44,11 @@ export function DebugJsonPage({
|
|
|
38
44
|
logLabel,
|
|
39
45
|
}: DebugJsonPageProps) {
|
|
40
46
|
const setCurrent = useRenderStore((s) => s.setCurrent);
|
|
47
|
+
const listMaxNested = useRenderStore((s) => s.listMaxNested);
|
|
48
|
+
const setListMaxNested = useRenderStore((s) => s.setListMaxNested);
|
|
49
|
+
const [listMaxNestedInput, setListMaxNestedInput] = useState(
|
|
50
|
+
String(listMaxNested),
|
|
51
|
+
);
|
|
41
52
|
const canTogglePreviewMode = typeof setPreviewMode === 'function';
|
|
42
53
|
const canToggleTheme =
|
|
43
54
|
typeof setAppConfig === 'function' && typeof appConfig?.theme === 'string';
|
|
@@ -58,6 +69,81 @@ export function DebugJsonPage({
|
|
|
58
69
|
return value as Node;
|
|
59
70
|
};
|
|
60
71
|
|
|
72
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
73
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function migrateStyleToStyles(node: Node): Node {
|
|
77
|
+
if (isNodeNullOrUndefined(node) || isNodeString(node)) {
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
if (isNodeArray(node)) {
|
|
81
|
+
const arr = node as Node[];
|
|
82
|
+
return arr.map((n) => migrateStyleToStyles(n));
|
|
83
|
+
}
|
|
84
|
+
if (!isPlainObject(node)) {
|
|
85
|
+
return node;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const record = node as unknown as NodeData<NodeDefaultAttribute>;
|
|
89
|
+
const nextChildren = migrateStyleToStyles(record.children);
|
|
90
|
+
|
|
91
|
+
if (!isPlainObject(record.attributes)) {
|
|
92
|
+
return { ...record, children: nextChildren };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const attrs = { ...record.attributes };
|
|
96
|
+
if ('style' in attrs && isPlainObject(attrs.style)) {
|
|
97
|
+
attrs.styles = attrs.style;
|
|
98
|
+
delete attrs.style;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...record,
|
|
103
|
+
children: nextChildren,
|
|
104
|
+
attributes: attrs,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const hasStyleAttribute = useMemo(() => {
|
|
109
|
+
function checkNode(n: Node): boolean {
|
|
110
|
+
if (isNodeNullOrUndefined(n) || isNodeString(n)) return false;
|
|
111
|
+
if (isNodeArray(n)) {
|
|
112
|
+
const arr = n as Node[];
|
|
113
|
+
return arr.some(checkNode);
|
|
114
|
+
}
|
|
115
|
+
if (!isPlainObject(n)) return false;
|
|
116
|
+
const record = n as unknown as NodeData<NodeDefaultAttribute>;
|
|
117
|
+
if (isPlainObject(record.attributes) && 'style' in record.attributes) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return checkNode(record.children);
|
|
121
|
+
}
|
|
122
|
+
return checkNode(data);
|
|
123
|
+
}, [data]);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
setListMaxNestedInput(String(listMaxNested));
|
|
127
|
+
}, [listMaxNested]);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
const handle = window.setTimeout(() => {
|
|
131
|
+
const next = Number.parseInt(listMaxNestedInput, 10);
|
|
132
|
+
if (!Number.isFinite(next) || next <= 0) return;
|
|
133
|
+
setListMaxNested(next);
|
|
134
|
+
}, 300);
|
|
135
|
+
return () => window.clearTimeout(handle);
|
|
136
|
+
}, [listMaxNestedInput, setListMaxNested]);
|
|
137
|
+
|
|
138
|
+
const handleFixStyleToStyles = () => {
|
|
139
|
+
if (!data) return;
|
|
140
|
+
const migrated = migrateStyleToStyles(data);
|
|
141
|
+
// Don't call analyseAndProccess here - let the user apply changes manually
|
|
142
|
+
// This avoids validation errors for unknown attributes that may exist
|
|
143
|
+
setData(migrated as Node);
|
|
144
|
+
setCurrent(migrated as Node);
|
|
145
|
+
};
|
|
146
|
+
|
|
61
147
|
return (
|
|
62
148
|
<>
|
|
63
149
|
<div className="modal__header localication-modal__header">
|
|
@@ -87,6 +173,17 @@ export function DebugJsonPage({
|
|
|
87
173
|
Log store
|
|
88
174
|
</button>
|
|
89
175
|
|
|
176
|
+
{hasStyleAttribute ? (
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
className="editor-button"
|
|
180
|
+
title="Migrate attributes.style to attributes.styles (schemaVersion=2)"
|
|
181
|
+
onClick={handleFixStyleToStyles}
|
|
182
|
+
>
|
|
183
|
+
Fix style → styles
|
|
184
|
+
</button>
|
|
185
|
+
) : null}
|
|
186
|
+
|
|
90
187
|
{onClose ? (
|
|
91
188
|
<button
|
|
92
189
|
type="button"
|
|
@@ -134,6 +231,17 @@ export function DebugJsonPage({
|
|
|
134
231
|
</div>
|
|
135
232
|
) : null}
|
|
136
233
|
|
|
234
|
+
<div className="localication-modal__controls">
|
|
235
|
+
<label>List max nested depth</label>
|
|
236
|
+
<input
|
|
237
|
+
type="number"
|
|
238
|
+
min={1}
|
|
239
|
+
className="input"
|
|
240
|
+
value={listMaxNestedInput}
|
|
241
|
+
onChange={(event) => setListMaxNestedInput(event.target.value)}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
137
245
|
<div className="localication-modal__editor">
|
|
138
246
|
<JsonTextEditor
|
|
139
247
|
rootName="node"
|
|
@@ -265,7 +265,7 @@ export function ProjectPage({
|
|
|
265
265
|
setEditorData(processed);
|
|
266
266
|
setCurrent(processed);
|
|
267
267
|
} catch (error) {
|
|
268
|
-
|
|
268
|
+
logger.error('ProjectPage', 'Failed to process node', error);
|
|
269
269
|
setValidationError(
|
|
270
270
|
error instanceof Error ? error.message : 'Node is not valid',
|
|
271
271
|
);
|
|
@@ -601,7 +601,7 @@ export function ProjectPage({
|
|
|
601
601
|
<div
|
|
602
602
|
style={{
|
|
603
603
|
// Set as a CSS variable so `.dark .split-right` can override it.
|
|
604
|
-
|
|
604
|
+
|
|
605
605
|
['--rb-canvas-bg' as any]: `url(${getImage(
|
|
606
606
|
TribeAssetName.Background,
|
|
607
607
|
)})`,
|
package/src/store.ts
CHANGED
|
@@ -76,6 +76,8 @@ type RenderStore = {
|
|
|
76
76
|
// Cache loaded font families to avoid repeated loads
|
|
77
77
|
loadedFonts: string[];
|
|
78
78
|
markFontLoaded: (fontFamily: string) => void;
|
|
79
|
+
listMaxNested: number;
|
|
80
|
+
setListMaxNested: (depth: number) => void;
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
@@ -233,6 +235,11 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
|
233
235
|
if (prev.includes(family)) return state;
|
|
234
236
|
return { loadedFonts: [...prev, family] };
|
|
235
237
|
}),
|
|
238
|
+
listMaxNested: 3,
|
|
239
|
+
setListMaxNested: (depth) =>
|
|
240
|
+
set({
|
|
241
|
+
listMaxNested: Number.isFinite(depth) && depth > 0 ? depth : 5,
|
|
242
|
+
}),
|
|
236
243
|
}),
|
|
237
244
|
{
|
|
238
245
|
name: 'render-store',
|
|
@@ -242,6 +249,7 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
|
242
249
|
logLevel: state.logLevel,
|
|
243
250
|
products: state.products,
|
|
244
251
|
benefits: state.benefits,
|
|
252
|
+
listMaxNested: state.listMaxNested,
|
|
245
253
|
}),
|
|
246
254
|
storage: createJSONStorage(() => localStorage),
|
|
247
255
|
},
|
|
@@ -94,12 +94,27 @@
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
.editor-device-button
|
|
97
|
+
.editor-device-button span {
|
|
98
98
|
position: absolute;
|
|
99
99
|
bottom: 4px;
|
|
100
100
|
right: 4px;
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
background-color: colors.$muted;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
display: block;
|
|
104
|
+
width: 22px;
|
|
105
|
+
height: 22px;
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.editor-device-button img {
|
|
110
|
+
position: absolute;
|
|
111
|
+
top: 50%;
|
|
112
|
+
left: 50%;
|
|
113
|
+
transform: translate(-50%, -50%);
|
|
114
|
+
max-width: 11px;
|
|
115
|
+
max-height: 11px;
|
|
116
|
+
width: auto;
|
|
117
|
+
height: auto;
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
.editor-header__actions {
|
|
@@ -4,20 +4,3 @@
|
|
|
4
4
|
height: 1px;
|
|
5
5
|
width: 100%;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
.onboard__buttons {
|
|
9
|
-
display: flex;
|
|
10
|
-
height: 40px;
|
|
11
|
-
gap: 12px;
|
|
12
|
-
align-items: center;
|
|
13
|
-
justify-content: center;
|
|
14
|
-
margin: 12px 24px;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.onboard__buttons--row {
|
|
18
|
-
flex-direction: row;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.onboard__buttons--column {
|
|
22
|
-
flex-direction: column;
|
|
23
|
-
}
|
|
@@ -78,10 +78,7 @@ $primaryForeground: hsl(
|
|
|
78
78
|
);
|
|
79
79
|
$destructive: hsl(var(--destructive, var(--rb-destructive, 0 84.2% 60.2%)));
|
|
80
80
|
$destructiveForeground: hsl(
|
|
81
|
-
var(
|
|
82
|
-
--destructive-foreground,
|
|
83
|
-
var(--rb-destructive-foreground, 0 0% 100%)
|
|
84
|
-
)
|
|
81
|
+
var(--destructive-foreground, var(--rb-destructive-foreground, 0 0% 100%))
|
|
85
82
|
);
|
|
86
83
|
$border: hsl(var(--border, var(--rb-border, 220 13% 91%)));
|
|
87
84
|
|
|
@@ -147,11 +147,19 @@
|
|
|
147
147
|
position: absolute;
|
|
148
148
|
bottom: -10px;
|
|
149
149
|
font-size: 8px;
|
|
150
|
+
left: 0;
|
|
151
|
+
right: 0;
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
justify-content: space-between;
|
|
155
|
+
padding-left: sizes.$spaceComfy;
|
|
156
|
+
padding-right: sizes.$spaceTight;
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
.builder__node {
|
|
153
160
|
@include card;
|
|
154
161
|
padding: sizes.$spaceSnug;
|
|
162
|
+
box-shadow: none;
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
.builder__node-type {
|
|
@@ -178,6 +186,18 @@
|
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
188
|
|
|
189
|
+
.builder__button-condition-text {
|
|
190
|
+
color: colors.$mutedTextColor;
|
|
191
|
+
white-space: nowrap;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
text-overflow: ellipsis;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.builder__button-children-count {
|
|
197
|
+
color: colors.$mutedTextColor;
|
|
198
|
+
white-space: nowrap;
|
|
199
|
+
}
|
|
200
|
+
|
|
181
201
|
.builder__text,
|
|
182
202
|
.builder__placeholder {
|
|
183
203
|
color: colors.$mutedTextColor;
|
|
@@ -78,38 +78,6 @@
|
|
|
78
78
|
height: 50px;
|
|
79
79
|
margin: 0 auto;
|
|
80
80
|
}
|
|
81
|
-
.embla__dot {
|
|
82
|
-
-webkit-tap-highlight-color: rgba(var(--text-high-contrast-rgb-value), 0.5);
|
|
83
|
-
-webkit-appearance: none;
|
|
84
|
-
appearance: none;
|
|
85
|
-
background-color: transparent;
|
|
86
|
-
--embla-dot-color: var(--detail-medium-contrast);
|
|
87
|
-
touch-action: manipulation;
|
|
88
|
-
display: inline-flex;
|
|
89
|
-
text-decoration: none;
|
|
90
|
-
cursor: pointer;
|
|
91
|
-
border: 0;
|
|
92
|
-
padding: 0;
|
|
93
|
-
margin: 0;
|
|
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));
|
|
97
|
-
display: flex;
|
|
98
|
-
align-items: center;
|
|
99
|
-
border-radius: 50%;
|
|
100
|
-
}
|
|
101
|
-
.embla__dot:after {
|
|
102
|
-
background-color: var(--embla-dot-color);
|
|
103
|
-
width: var(--embla-dot-size, 1.4rem);
|
|
104
|
-
height: var(--embla-dot-size, 1.4rem);
|
|
105
|
-
border-radius: 50%;
|
|
106
|
-
display: flex;
|
|
107
|
-
align-items: center;
|
|
108
|
-
content: '';
|
|
109
|
-
}
|
|
110
|
-
.embla__dot--selected {
|
|
111
|
-
--embla-dot-color: var(--text-body);
|
|
112
|
-
}
|
|
113
81
|
.carousel-provider {
|
|
114
82
|
height: 100%;
|
|
115
83
|
}
|