@developer_tribe/react-builder 1.2.8 → 1.2.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/AttributesEditor.d.ts +2 -11
- package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +10 -0
- package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +9 -0
- package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +8 -0
- package/dist/attribute-analyser/style/web/useExtractImageStyle.d.ts +4 -0
- package/dist/attribute-analyser/style/web/useExtractTextStyle.d.ts +4 -0
- package/dist/attribute-analyser/style/web/useExtractViewStyle.d.ts +4 -0
- package/dist/attributes-editor/AttributesEditorFields.d.ts +18 -0
- package/dist/attributes-editor/AttributesEditorView.d.ts +4 -0
- package/dist/attributes-editor/attributesEditorModelTypes.d.ts +67 -0
- package/dist/attributes-editor/attributesEditorUtils.d.ts +19 -0
- package/dist/attributes-editor/useAttributesEditorModel.d.ts +2 -0
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +6 -6
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +3 -3
- package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -1
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +2 -2
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +4 -4
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +3 -3
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -1
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -1
- package/dist/build-components/Image/ImageProps.generated.d.ts +5 -3
- package/dist/build-components/Main/MainProps.generated.d.ts +2 -2
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +9 -8
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +11 -11
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +15 -9
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +10 -10
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +8 -6
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +6 -3
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +5 -4
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +3 -3
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +3 -3
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +6 -6
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +1 -1
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +4 -4
- package/dist/build-components/Text/TextProps.generated.d.ts +3 -3
- package/dist/build-components/View/ViewProps.generated.d.ts +1 -1
- package/dist/build-components/patterns.generated.d.ts +2690 -5804
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.cjs.js +6 -4
- package/dist/index.native.cjs.js.map +1 -1
- package/dist/index.native.d.ts +5 -6
- package/dist/index.native.esm.js +6 -4
- package/dist/index.native.esm.js.map +1 -1
- package/dist/migrations/migratePipe.d.ts +1 -1
- package/dist/migrations/migrations/1.1.2_extract_component_attributes_from_style.d.ts +2 -0
- package/dist/mockOS/components/PermissionModal.d.ts +1 -2
- package/dist/styles.css +1 -1
- package/dist/types/PreviewConfig.d.ts +1 -5
- package/dist/utils/getMeta.d.ts +5 -0
- package/dist/utils/patterns.d.ts +12 -0
- package/package.json +2 -1
- package/scripts/prebuild/prebuild.js +14 -0
- package/scripts/prebuild/utils/createGeneratedProps.js +19 -13
- package/scripts/prebuild/utils/index.js +1 -0
- package/scripts/prebuild/utils/updateMetaJson.js +66 -0
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +37 -3
- package/scripts/prebuild/utils/validatePatternJson.js +27 -2
- package/scripts/public/scripts/build/index.js +20 -3
- package/scripts/public/scripts/build/info.json +6 -0
- package/scripts/public/scripts/build/utils/createComponentsIndex.js +9 -3
- package/scripts/public/scripts/build/utils/createRenderNodeGenerated.js +66 -8
- package/src/AttributesEditor.tsx +8 -944
- package/src/assets/meta.json +4 -0
- package/src/assets/samples/carousel-sample.json +1 -1
- package/src/assets/samples/getSamples.ts +2 -0
- package/src/assets/samples/paywall-1.json +11 -7
- package/src/assets/samples/simple-1.json +3 -3
- package/src/assets/samples/simple-2.json +3 -3
- package/src/assets/samples/unmigrated-builder-1.1.1.json +87 -0
- package/src/assets/samples/unmigrated-builder1.json +1 -1
- package/src/assets/samples/unvalidated-builder1.json +3 -3
- package/src/assets/samples/unvalidated-crash1.json +1 -1
- package/src/assets/samples/unvalidated-crashcomponent1.json +1 -1
- package/src/assets/samples/vpn-onboard-1.json +1 -1
- package/src/assets/samples/vpn-onboard-2.json +1 -1
- package/src/assets/samples/vpn-onboard-3.json +1 -1
- package/src/assets/samples/vpn-onboard-4.json +1 -1
- package/src/assets/samples/vpn-onboard-5.json +1 -1
- package/src/assets/samples/vpn-onboard-6.json +1 -1
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +46 -0
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +50 -0
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +32 -0
- package/src/attribute-analyser/style/web/useExtractImageStyle.ts +20 -0
- package/src/attribute-analyser/style/web/useExtractTextStyle.ts +27 -0
- package/src/attribute-analyser/style/web/useExtractViewStyle.ts +20 -0
- package/src/attributes-editor/AttributesEditorFields.tsx +248 -0
- package/src/attributes-editor/AttributesEditorView.tsx +360 -0
- package/src/attributes-editor/attributesEditorModelTypes.ts +86 -0
- package/src/attributes-editor/attributesEditorUtils.ts +102 -0
- package/src/attributes-editor/useAttributesEditorModel.ts +477 -0
- package/src/build-components/BIcon/BIcon.tsx +4 -4
- package/src/build-components/BIcon/BIconProps.generated.ts +6 -6
- package/src/build-components/BIcon/pattern.json +5 -6
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +3 -2
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +3 -3
- package/src/build-components/Button/Button.tsx +2 -2
- package/src/build-components/Button/ButtonProps.generated.ts +1 -1
- package/src/build-components/Button/pattern.json +17 -15
- package/src/build-components/Carousel/Carousel.tsx +1 -1
- package/src/build-components/Carousel/CarouselProps.generated.ts +2 -2
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +4 -7
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +7 -7
- package/src/build-components/CarouselButtons/pattern.json +2 -1
- package/src/build-components/CarouselDots/CarouselDots.tsx +2 -2
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +9 -9
- package/src/build-components/CarouselItem/CarouselItem.tsx +1 -1
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -1
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +1 -1
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -1
- package/src/build-components/Image/Image.tsx +1 -1
- package/src/build-components/Image/ImageProps.generated.ts +5 -3
- package/src/build-components/Image/pattern.json +10 -9
- package/src/build-components/Main/Main.tsx +2 -2
- package/src/build-components/Main/MainProps.generated.ts +2 -2
- package/src/build-components/Main/pattern.json +2 -1
- package/src/build-components/Onboard/OnboardProps.generated.ts +1 -1
- package/src/build-components/OnboardButton/OnboardButton.tsx +7 -6
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +14 -13
- package/src/build-components/OnboardButton/pattern.json +9 -7
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +31 -31
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +14 -14
- package/src/build-components/OnboardButtons/pattern.json +9 -7
- package/src/build-components/OnboardDot/OnboardDot.tsx +7 -6
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +26 -9
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +17 -16
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +10 -10
- package/src/build-components/OnboardFooter/pattern.json +16 -14
- package/src/build-components/OnboardImage/OnboardImage.tsx +8 -8
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +8 -6
- package/src/build-components/OnboardImage/pattern.json +2 -1
- package/src/build-components/OnboardItem/OnboardItem.tsx +1 -1
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +6 -3
- package/src/build-components/OnboardItem/pattern.json +2 -1
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +1 -1
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +5 -4
- package/src/build-components/OnboardProvider/pattern.json +2 -1
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +3 -3
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +3 -3
- package/src/build-components/PaywallBackground/PaywallBackground.tsx +1 -1
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +1 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +5 -4
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +6 -6
- package/src/build-components/PaywallOptions/PaywallOptionButton.tsx +1 -1
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
- package/src/build-components/PaywallProvider/PaywallProvider.tsx +1 -1
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -1
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +1 -1
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +1 -1
- package/src/build-components/RadioButton/RadioButton.tsx +5 -5
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +4 -4
- package/src/build-components/RadioButton/pattern.json +9 -7
- package/src/build-components/Text/Text.tsx +6 -6
- package/src/build-components/Text/TextProps.generated.ts +3 -3
- package/src/build-components/Text/pattern.json +15 -11
- package/src/build-components/View/View.tsx +1 -1
- package/src/build-components/View/ViewProps.generated.ts +1 -1
- package/src/build-components/View/pattern.json +71 -66
- package/src/build-components/patterns.generated.ts +3059 -6008
- package/src/components/AttributesEditorPanel.tsx +2 -2
- package/src/index.native.ts +6 -9
- package/src/index.ts +4 -0
- package/src/migrations/migratePipe.ts +7 -3
- package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +211 -0
- package/src/mockOS/components/MockOSRouter.tsx +3 -1
- package/src/mockOS/components/PermissionModal.tsx +20 -160
- package/src/mockOS/components/SubscriptionModal.tsx +41 -278
- package/src/pages/ProjectPage.tsx +12 -6
- package/src/styles/components/_attributes-editor.scss +122 -0
- package/src/styles/components/_mockos-router.scss +388 -0
- package/src/styles/components/_onboard.scss +23 -0
- package/src/styles/index.scss +1 -0
- package/src/types/PreviewConfig.ts +1 -5
- package/src/utils/analyseNodeByPatterns.ts +39 -4
- package/src/utils/extractTextStyle/extractTextStyle.ts +4 -1
- package/src/utils/getMeta.ts +15 -0
- package/src/utils/patterns.ts +47 -4
- package/dist/hooks/useExtractImageStyle.d.ts +0 -5
- package/dist/hooks/useExtractTextStyle.d.ts +0 -3
- package/dist/hooks/useExtractViewStyle.d.ts +0 -3
- package/src/hooks/useExtractImageStyle.ts +0 -30
- package/src/hooks/useExtractTextStyle.ts +0 -34
- package/src/hooks/useExtractViewStyle.ts +0 -30
- package/src/migrations/migrations/1.1.0_normalize_style_attributes.ts +0 -80
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { NodeDefaultAttribute } from '../types/Node';
|
|
2
|
+
import type { SchemaEntry } from './types';
|
|
3
|
+
|
|
4
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type StyleBagSource = 'styles' | 'style' | 'none';
|
|
9
|
+
|
|
10
|
+
export function getStyleBagSourceAndValue(attributes: NodeDefaultAttribute): {
|
|
11
|
+
source: StyleBagSource;
|
|
12
|
+
styleBag?: Record<string, unknown>;
|
|
13
|
+
} {
|
|
14
|
+
const record = (attributes ?? {}) as Record<string, unknown>;
|
|
15
|
+
const maybeStyles = record.styles;
|
|
16
|
+
if (isPlainObject(maybeStyles)) {
|
|
17
|
+
return { source: 'styles', styleBag: maybeStyles };
|
|
18
|
+
}
|
|
19
|
+
const maybeStyle = record.style;
|
|
20
|
+
if (isPlainObject(maybeStyle)) {
|
|
21
|
+
return { source: 'style', styleBag: maybeStyle };
|
|
22
|
+
}
|
|
23
|
+
return { source: 'none', styleBag: undefined };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type AttributeTypeSpec = SchemaEntry['type'];
|
|
27
|
+
|
|
28
|
+
function isAttributeTypeSpec(value: unknown): value is AttributeTypeSpec {
|
|
29
|
+
return typeof value === 'string' || Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getNestedStyleSchema(
|
|
33
|
+
schema: Record<string, unknown>,
|
|
34
|
+
): Record<string, AttributeTypeSpec> | undefined {
|
|
35
|
+
const maybe = schema.styles ?? schema.style;
|
|
36
|
+
if (!isPlainObject(maybe)) return undefined;
|
|
37
|
+
const out: Record<string, AttributeTypeSpec> = {};
|
|
38
|
+
for (const [k, v] of Object.entries(maybe)) {
|
|
39
|
+
if (isAttributeTypeSpec(v)) out[k] = v;
|
|
40
|
+
}
|
|
41
|
+
return Object.keys(out).length ? out : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Flattens schemaVersion=2 nested style schemas (under schema.style or schema.styles)
|
|
46
|
+
* into plain entries so the editor can list them as individual fields.
|
|
47
|
+
*
|
|
48
|
+
* Note: we intentionally exclude the raw `style` / `styles` keys themselves, because
|
|
49
|
+
* Field.tsx does not render object specs (---not-implemented----).
|
|
50
|
+
*/
|
|
51
|
+
export function buildAttributesEditorEntries(
|
|
52
|
+
schema: Record<string, unknown>,
|
|
53
|
+
attributeMeta?: Record<string, { category?: string }>,
|
|
54
|
+
): SchemaEntry[] {
|
|
55
|
+
const nestedStyleSchema = getNestedStyleSchema(schema);
|
|
56
|
+
const metaKeys = Object.keys(attributeMeta ?? {});
|
|
57
|
+
const topLevelSchemaKeys = Object.keys(schema).filter(
|
|
58
|
+
(k) => k !== 'style' && k !== 'styles',
|
|
59
|
+
);
|
|
60
|
+
const nestedStyleKeys = Object.keys(nestedStyleSchema ?? {});
|
|
61
|
+
const keySet = new Set<string>([
|
|
62
|
+
...metaKeys,
|
|
63
|
+
...topLevelSchemaKeys,
|
|
64
|
+
...nestedStyleKeys,
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
const keys = Array.from(keySet).filter(
|
|
68
|
+
(k) => k !== 'style' && k !== 'styles',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const entries: SchemaEntry[] = [];
|
|
72
|
+
keys.forEach((name) => {
|
|
73
|
+
const top = (schema as Record<string, unknown>)[name];
|
|
74
|
+
const nested = nestedStyleSchema?.[name];
|
|
75
|
+
const spec = isAttributeTypeSpec(top) ? top : nested;
|
|
76
|
+
if (!spec) return;
|
|
77
|
+
entries.push({ name, type: spec });
|
|
78
|
+
});
|
|
79
|
+
return entries;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isValidStyleBagValue(
|
|
83
|
+
attributes: NodeDefaultAttribute,
|
|
84
|
+
): boolean {
|
|
85
|
+
const record = attributes as Record<string, unknown>;
|
|
86
|
+
const maybeStyles = record?.styles;
|
|
87
|
+
const maybeStyle = record?.style;
|
|
88
|
+
return !(
|
|
89
|
+
(maybeStyles != null && !isPlainObject(maybeStyles)) ||
|
|
90
|
+
(maybeStyle != null && !isPlainObject(maybeStyle))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function findLegacyFlatStyleKeys(
|
|
95
|
+
attributes: NodeDefaultAttribute,
|
|
96
|
+
isStyleKeyForWrite: (name: string) => boolean,
|
|
97
|
+
): string[] {
|
|
98
|
+
if (!isPlainObject(attributes)) return [];
|
|
99
|
+
return Object.keys(attributes).filter(
|
|
100
|
+
(k) => k !== 'style' && k !== 'styles' && isStyleKeyForWrite(k),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import type { NodeData, NodeDefaultAttribute } from '../types/Node';
|
|
3
|
+
import type { ProjectColorTokenMap, ProjectColors } from '../types/Project';
|
|
4
|
+
import type { ViewPropsGenerated } from '../build-components/View/ViewProps.generated';
|
|
5
|
+
import useNode from '../build-components/useNode';
|
|
6
|
+
import { isNodeString } from '../utils/analyseNode';
|
|
7
|
+
import { logger } from '../utils/logger';
|
|
8
|
+
import {
|
|
9
|
+
getAttributeMeta,
|
|
10
|
+
getAttributeSchema,
|
|
11
|
+
getPatternByType,
|
|
12
|
+
getStyleAttributeKeySet,
|
|
13
|
+
} from '../utils/patterns';
|
|
14
|
+
import { useRenderStore } from '../store';
|
|
15
|
+
import type { LayoutContext, SchemaEntry } from './types';
|
|
16
|
+
import {
|
|
17
|
+
buildAttributesEditorEntries,
|
|
18
|
+
findLegacyFlatStyleKeys,
|
|
19
|
+
getStyleBagSourceAndValue,
|
|
20
|
+
isValidStyleBagValue,
|
|
21
|
+
} from './attributesEditorUtils';
|
|
22
|
+
import type {
|
|
23
|
+
AttributesEditorModel,
|
|
24
|
+
AttributesEditorProps,
|
|
25
|
+
AttributesEditorSpecialSection,
|
|
26
|
+
AttributesEditorTabConfig,
|
|
27
|
+
TabContentInfo,
|
|
28
|
+
TabId,
|
|
29
|
+
} from './attributesEditorModelTypes';
|
|
30
|
+
|
|
31
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
32
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildProjectColorsFallback(appConfig: any): ProjectColors | undefined {
|
|
36
|
+
const addColor = (collection: Set<string>, value?: string) => {
|
|
37
|
+
if (typeof value !== 'string') return;
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!trimmed) return;
|
|
40
|
+
collection.add(trimmed);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const fallback = new Set<string>();
|
|
44
|
+
const styles = [
|
|
45
|
+
appConfig?.screenStyle?.light,
|
|
46
|
+
appConfig?.screenStyle?.dark,
|
|
47
|
+
].filter(Boolean) as Array<{
|
|
48
|
+
backgroundColor?: string;
|
|
49
|
+
color?: string;
|
|
50
|
+
seperatorColor?: string;
|
|
51
|
+
}>;
|
|
52
|
+
styles.forEach((style) => {
|
|
53
|
+
['backgroundColor', 'color', 'seperatorColor'].forEach((key) => {
|
|
54
|
+
const value = style?.[key as keyof typeof style];
|
|
55
|
+
addColor(fallback, value);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (fallback.size === 0) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fallbackRecord: ProjectColorTokenMap = {};
|
|
64
|
+
Array.from(fallback).forEach((color, index) => {
|
|
65
|
+
fallbackRecord[`FALLBACK_${index + 1}`] = color;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { STATIC_COLORS: fallbackRecord };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useAttributesEditorModel({
|
|
72
|
+
node,
|
|
73
|
+
onChange,
|
|
74
|
+
projectColors,
|
|
75
|
+
}: AttributesEditorProps): AttributesEditorModel {
|
|
76
|
+
const isInvalidNode = !node || isNodeString(node);
|
|
77
|
+
const baseData = isInvalidNode
|
|
78
|
+
? ({
|
|
79
|
+
type: 'View',
|
|
80
|
+
children: null,
|
|
81
|
+
attributes: {},
|
|
82
|
+
} as unknown as NodeData<NodeDefaultAttribute>)
|
|
83
|
+
: (node as NodeData<NodeDefaultAttribute>);
|
|
84
|
+
|
|
85
|
+
const data = useNode(baseData);
|
|
86
|
+
const {
|
|
87
|
+
appConfig,
|
|
88
|
+
fonts: projectFonts,
|
|
89
|
+
loadedFonts,
|
|
90
|
+
markFontLoaded,
|
|
91
|
+
addError,
|
|
92
|
+
} = useRenderStore((state) => ({
|
|
93
|
+
appConfig: state.appConfig,
|
|
94
|
+
fonts: state.fonts,
|
|
95
|
+
loadedFonts: state.loadedFonts,
|
|
96
|
+
markFontLoaded: state.markFontLoaded,
|
|
97
|
+
addError: state.addError,
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const schema = (getAttributeSchema(data?.type) ?? {}) as Record<
|
|
101
|
+
string,
|
|
102
|
+
unknown
|
|
103
|
+
>;
|
|
104
|
+
const attributeMeta = getAttributeMeta(data?.type);
|
|
105
|
+
const attributes = (data?.attributes ?? {}) as NodeDefaultAttribute;
|
|
106
|
+
|
|
107
|
+
const styleAttributeKeys = useMemo(() => getStyleAttributeKeySet(), []);
|
|
108
|
+
const styleBagInfo = useMemo(
|
|
109
|
+
() => getStyleBagSourceAndValue(attributes),
|
|
110
|
+
[attributes],
|
|
111
|
+
);
|
|
112
|
+
const styleBag = styleBagInfo.styleBag;
|
|
113
|
+
|
|
114
|
+
const isStyleKeyForWrite = useCallback(
|
|
115
|
+
(name: string) => {
|
|
116
|
+
const metaCategory = attributeMeta?.[name]?.category;
|
|
117
|
+
if (metaCategory === 'style') return true;
|
|
118
|
+
return styleAttributeKeys.has(name);
|
|
119
|
+
},
|
|
120
|
+
[attributeMeta, styleAttributeKeys],
|
|
121
|
+
);
|
|
122
|
+
const isStyleKeyForRead = useCallback(
|
|
123
|
+
(name: string) => {
|
|
124
|
+
if (isStyleKeyForWrite(name)) return true;
|
|
125
|
+
return !!(
|
|
126
|
+
styleBag && Object.prototype.hasOwnProperty.call(styleBag, name)
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
[isStyleKeyForWrite, styleBag],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const legacyFlatStyleKeys = useMemo(
|
|
133
|
+
() => findLegacyFlatStyleKeys(attributes, isStyleKeyForWrite),
|
|
134
|
+
[attributes, isStyleKeyForWrite],
|
|
135
|
+
);
|
|
136
|
+
const hasInvalidStyleBag = useMemo(
|
|
137
|
+
() => !isValidStyleBagValue(attributes),
|
|
138
|
+
[attributes],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const hasLegacyStyleKey = styleBagInfo.source === 'style';
|
|
143
|
+
if (
|
|
144
|
+
legacyFlatStyleKeys.length === 0 &&
|
|
145
|
+
!hasInvalidStyleBag &&
|
|
146
|
+
!hasLegacyStyleKey
|
|
147
|
+
) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
logger.warn(
|
|
151
|
+
'AttributesEditor',
|
|
152
|
+
'Legacy/unmigrated attributes detected. This editor expects schemaVersion=2 (style nested under attributes.styles). Please run migrations.',
|
|
153
|
+
{
|
|
154
|
+
componentType: data?.type,
|
|
155
|
+
nodeKey: (baseData as any)?.key,
|
|
156
|
+
legacyFlatStyleKeys,
|
|
157
|
+
hasInvalidStyleBag,
|
|
158
|
+
styleBagSource: styleBagInfo.source,
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
}, [
|
|
162
|
+
baseData,
|
|
163
|
+
data?.type,
|
|
164
|
+
hasInvalidStyleBag,
|
|
165
|
+
legacyFlatStyleKeys,
|
|
166
|
+
styleBagInfo.source,
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const getAttributeValue = useCallback(
|
|
170
|
+
(name: string) => {
|
|
171
|
+
if (isStyleKeyForRead(name)) {
|
|
172
|
+
return styleBag?.[name];
|
|
173
|
+
}
|
|
174
|
+
return (attributes as Record<string, unknown>)?.[name];
|
|
175
|
+
},
|
|
176
|
+
[attributes, isStyleKeyForRead, styleBag],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const projectColorsForPicker = useMemo<ProjectColors | undefined>(() => {
|
|
180
|
+
if (projectColors) return projectColors;
|
|
181
|
+
return buildProjectColorsFallback(appConfig);
|
|
182
|
+
}, [appConfig, projectColors]);
|
|
183
|
+
|
|
184
|
+
const viewAttributes = useMemo<
|
|
185
|
+
Partial<ViewPropsGenerated['attributes']> | undefined
|
|
186
|
+
>(
|
|
187
|
+
() =>
|
|
188
|
+
data?.type === 'View'
|
|
189
|
+
? (attributes as ViewPropsGenerated['attributes'])
|
|
190
|
+
: undefined,
|
|
191
|
+
[attributes, data?.type],
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const layoutContext = useMemo<LayoutContext>(
|
|
195
|
+
() => ({
|
|
196
|
+
flexDirection: styleBag?.flexDirection as LayoutContext['flexDirection'],
|
|
197
|
+
alignItems: styleBag?.alignItems as LayoutContext['alignItems'],
|
|
198
|
+
justifyContent:
|
|
199
|
+
styleBag?.justifyContent as LayoutContext['justifyContent'],
|
|
200
|
+
}),
|
|
201
|
+
[styleBag?.flexDirection, styleBag?.alignItems, styleBag?.justifyContent],
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const patternForType = useMemo(
|
|
205
|
+
() => (data?.type ? getPatternByType(data.type) : undefined),
|
|
206
|
+
[data?.type],
|
|
207
|
+
);
|
|
208
|
+
const componentMeta = patternForType?.meta;
|
|
209
|
+
|
|
210
|
+
const componentTitle = componentMeta?.label ?? data?.type ?? 'Component';
|
|
211
|
+
const componentDescription = componentMeta?.description;
|
|
212
|
+
|
|
213
|
+
const entries = useMemo(() => {
|
|
214
|
+
return buildAttributesEditorEntries(schema, attributeMeta).filter(
|
|
215
|
+
({ type }) => (typeof type === 'string' ? type !== 'never' : true),
|
|
216
|
+
);
|
|
217
|
+
}, [attributeMeta, schema]);
|
|
218
|
+
|
|
219
|
+
const visibleEntries = useMemo(() => {
|
|
220
|
+
if (!componentMeta?.hideAllAttributes) return entries;
|
|
221
|
+
return entries.filter(({ name }) => {
|
|
222
|
+
const meta = attributeMeta?.[name];
|
|
223
|
+
return meta?.forceVisible === true || meta?.override === true;
|
|
224
|
+
});
|
|
225
|
+
}, [attributeMeta, componentMeta?.hideAllAttributes, entries]);
|
|
226
|
+
|
|
227
|
+
const mockableFeatureKeys = useMemo(() => {
|
|
228
|
+
const mockable = componentMeta?.mockableFeatures;
|
|
229
|
+
if (!mockable || typeof mockable !== 'object') return [];
|
|
230
|
+
return Object.entries(mockable)
|
|
231
|
+
.filter(([, enabled]) => enabled === true)
|
|
232
|
+
.map(([key]) => key)
|
|
233
|
+
.filter((key) => typeof key === 'string' && key.trim().length > 0)
|
|
234
|
+
.sort((a, b) => a.localeCompare(b));
|
|
235
|
+
}, [componentMeta?.mockableFeatures]);
|
|
236
|
+
|
|
237
|
+
const [activeMockableFeature, setActiveMockableFeature] = useState<
|
|
238
|
+
string | null
|
|
239
|
+
>(null);
|
|
240
|
+
|
|
241
|
+
const groupedAndSpecialGroups = useMemo(() => {
|
|
242
|
+
const groups: Record<TabId, SchemaEntry[]> = {
|
|
243
|
+
style: [],
|
|
244
|
+
container: [],
|
|
245
|
+
other: [],
|
|
246
|
+
};
|
|
247
|
+
const specials: Record<string, SchemaEntry[]> = {};
|
|
248
|
+
|
|
249
|
+
const getSortOrder = (name: string) =>
|
|
250
|
+
attributeMeta?.[name]?.sort ?? Number.MAX_SAFE_INTEGER;
|
|
251
|
+
const compareEntries = (a: SchemaEntry, b: SchemaEntry) => {
|
|
252
|
+
const order = getSortOrder(a.name) - getSortOrder(b.name);
|
|
253
|
+
return order !== 0 ? order : a.name.localeCompare(b.name);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
visibleEntries.forEach(({ name, type }) => {
|
|
257
|
+
const meta = attributeMeta?.[name];
|
|
258
|
+
const specialCategory = meta?.specialCategory;
|
|
259
|
+
if (typeof specialCategory === 'string') {
|
|
260
|
+
const normalizedSpecialCategory = specialCategory.trim();
|
|
261
|
+
if (normalizedSpecialCategory) {
|
|
262
|
+
if (!specials[normalizedSpecialCategory]) {
|
|
263
|
+
specials[normalizedSpecialCategory] = [];
|
|
264
|
+
}
|
|
265
|
+
specials[normalizedSpecialCategory].push({ name, type });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const metaCategory = meta?.category;
|
|
271
|
+
const normalized =
|
|
272
|
+
metaCategory === 'style'
|
|
273
|
+
? 'style'
|
|
274
|
+
: metaCategory === 'container'
|
|
275
|
+
? 'container'
|
|
276
|
+
: 'other';
|
|
277
|
+
groups[normalized].push({ name, type });
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
Object.values(groups).forEach((list) => list.sort(compareEntries));
|
|
281
|
+
Object.values(specials).forEach((list) => list.sort(compareEntries));
|
|
282
|
+
return { grouped: groups, specialGroups: specials };
|
|
283
|
+
}, [attributeMeta, visibleEntries]);
|
|
284
|
+
|
|
285
|
+
const { grouped, specialGroups } = groupedAndSpecialGroups;
|
|
286
|
+
|
|
287
|
+
const specialSectionsByTab = useMemo<
|
|
288
|
+
Record<TabId, AttributesEditorSpecialSection[]>
|
|
289
|
+
>(() => {
|
|
290
|
+
const buckets: Record<TabId, AttributesEditorSpecialSection[]> = {
|
|
291
|
+
style: [],
|
|
292
|
+
container: [],
|
|
293
|
+
other: [],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const compareSections = (
|
|
297
|
+
a: AttributesEditorSpecialSection,
|
|
298
|
+
b: AttributesEditorSpecialSection,
|
|
299
|
+
) => {
|
|
300
|
+
const aSort = a.meta?.sort ?? Number.MAX_SAFE_INTEGER;
|
|
301
|
+
const bSort = b.meta?.sort ?? Number.MAX_SAFE_INTEGER;
|
|
302
|
+
const order = aSort - bSort;
|
|
303
|
+
return order !== 0 ? order : a.key.localeCompare(b.key);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
Object.entries(specialGroups).forEach(([categoryKey, categoryEntries]) => {
|
|
307
|
+
if (!categoryEntries.length) return;
|
|
308
|
+
const metaForCategory = componentMeta?.specialCategories?.[categoryKey];
|
|
309
|
+
const targetCategory = (
|
|
310
|
+
metaForCategory?.category === 'style'
|
|
311
|
+
? 'style'
|
|
312
|
+
: metaForCategory?.category === 'container'
|
|
313
|
+
? 'container'
|
|
314
|
+
: 'other'
|
|
315
|
+
) as TabId;
|
|
316
|
+
buckets[targetCategory].push({
|
|
317
|
+
key: categoryKey,
|
|
318
|
+
entries: categoryEntries,
|
|
319
|
+
meta: metaForCategory,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
(Object.keys(buckets) as TabId[]).forEach((tabId) => {
|
|
324
|
+
buckets[tabId].sort(compareSections);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return buckets;
|
|
328
|
+
}, [componentMeta?.specialCategories, specialGroups]);
|
|
329
|
+
|
|
330
|
+
const tabs = useMemo<AttributesEditorTabConfig[]>(
|
|
331
|
+
() => [
|
|
332
|
+
{ id: 'container', label: 'Container', entries: grouped.container },
|
|
333
|
+
{ id: 'style', label: 'Styles', entries: grouped.style },
|
|
334
|
+
{ id: 'other', label: 'Others', entries: grouped.other },
|
|
335
|
+
],
|
|
336
|
+
[grouped],
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const tabContentInfo = useMemo<TabContentInfo>(() => {
|
|
340
|
+
const info: TabContentInfo = {
|
|
341
|
+
style: { baseCount: 0, specialCount: 0 },
|
|
342
|
+
container: { baseCount: 0, specialCount: 0 },
|
|
343
|
+
other: { baseCount: 0, specialCount: 0 },
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
tabs.forEach((tab) => {
|
|
347
|
+
info[tab.id].baseCount = tab.entries.length;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
(Object.keys(specialSectionsByTab) as TabId[]).forEach((tabId) => {
|
|
351
|
+
info[tabId].specialCount = specialSectionsByTab[tabId].reduce(
|
|
352
|
+
(sum, section) => sum + section.entries.length,
|
|
353
|
+
0,
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return info;
|
|
358
|
+
}, [specialSectionsByTab, tabs]);
|
|
359
|
+
|
|
360
|
+
const firstAvailableTab = useMemo<TabId>(() => {
|
|
361
|
+
return (
|
|
362
|
+
tabs.find((tab) => {
|
|
363
|
+
const counts = tabContentInfo[tab.id];
|
|
364
|
+
return counts.baseCount + counts.specialCount > 0;
|
|
365
|
+
})?.id ?? 'other'
|
|
366
|
+
);
|
|
367
|
+
}, [tabContentInfo, tabs]);
|
|
368
|
+
|
|
369
|
+
const [activeTab, setActiveTab] = useState<TabId>(firstAvailableTab);
|
|
370
|
+
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
setActiveTab((prev) => {
|
|
373
|
+
const counts = tabContentInfo[prev];
|
|
374
|
+
if (counts && counts.baseCount + counts.specialCount > 0) {
|
|
375
|
+
return prev;
|
|
376
|
+
}
|
|
377
|
+
return firstAvailableTab;
|
|
378
|
+
});
|
|
379
|
+
}, [firstAvailableTab, tabContentInfo]);
|
|
380
|
+
|
|
381
|
+
const activeEntries =
|
|
382
|
+
tabs.find((tab) => tab.id === activeTab)?.entries ??
|
|
383
|
+
tabs.find((tab) => tab.id === firstAvailableTab)?.entries ??
|
|
384
|
+
[];
|
|
385
|
+
|
|
386
|
+
const activeSpecialSections = specialSectionsByTab[activeTab] ?? [];
|
|
387
|
+
|
|
388
|
+
const handleAttributeChange = useCallback(
|
|
389
|
+
(name: string, val: unknown) => {
|
|
390
|
+
const prevAttrs =
|
|
391
|
+
((baseData?.attributes ?? {}) as Record<string, unknown>) ?? {};
|
|
392
|
+
const isStyleKey = isStyleKeyForWrite(name);
|
|
393
|
+
const nextAttrs: Record<string, unknown> = { ...prevAttrs };
|
|
394
|
+
if (isStyleKey) {
|
|
395
|
+
const prevStyle = (
|
|
396
|
+
isPlainObject(nextAttrs.styles)
|
|
397
|
+
? nextAttrs.styles
|
|
398
|
+
: isPlainObject(nextAttrs.style)
|
|
399
|
+
? nextAttrs.style
|
|
400
|
+
: {}
|
|
401
|
+
) as Record<string, unknown>;
|
|
402
|
+
const nextStyle = { ...prevStyle, [name]: val };
|
|
403
|
+
// Keep both keys in sync:
|
|
404
|
+
// - `styles` is the canonical store for the editor (latest migration).
|
|
405
|
+
// - `style` is kept for back-compat because build-components currently read `attributes.style`.
|
|
406
|
+
nextAttrs.styles = nextStyle;
|
|
407
|
+
nextAttrs.style = nextStyle;
|
|
408
|
+
if (name in nextAttrs) delete nextAttrs[name];
|
|
409
|
+
} else {
|
|
410
|
+
nextAttrs[name] = val;
|
|
411
|
+
}
|
|
412
|
+
const next: NodeData<NodeDefaultAttribute> = {
|
|
413
|
+
...baseData,
|
|
414
|
+
attributes: nextAttrs as NodeDefaultAttribute,
|
|
415
|
+
};
|
|
416
|
+
onChange(next);
|
|
417
|
+
},
|
|
418
|
+
[baseData, isStyleKeyForWrite, onChange],
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const handleChildrenChange = useCallback(
|
|
422
|
+
(val: string) => {
|
|
423
|
+
const next: NodeData<NodeDefaultAttribute> = {
|
|
424
|
+
...baseData,
|
|
425
|
+
children: val,
|
|
426
|
+
};
|
|
427
|
+
onChange(next);
|
|
428
|
+
},
|
|
429
|
+
[baseData, onChange],
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const hasStringChildren =
|
|
433
|
+
!!patternForType?.pattern &&
|
|
434
|
+
(patternForType.pattern as { children?: unknown }).children === 'string';
|
|
435
|
+
const childrenValue =
|
|
436
|
+
typeof (baseData.children as unknown) === 'string'
|
|
437
|
+
? (baseData.children as string)
|
|
438
|
+
: '';
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
isInvalidNode,
|
|
442
|
+
baseData,
|
|
443
|
+
data,
|
|
444
|
+
appConfig,
|
|
445
|
+
projectFonts,
|
|
446
|
+
loadedFonts,
|
|
447
|
+
markFontLoaded,
|
|
448
|
+
addError,
|
|
449
|
+
schema,
|
|
450
|
+
attributeMeta,
|
|
451
|
+
componentTitle,
|
|
452
|
+
componentDescription,
|
|
453
|
+
patternForType,
|
|
454
|
+
componentMeta,
|
|
455
|
+
attributes,
|
|
456
|
+
styleBag,
|
|
457
|
+
projectColorsForPicker,
|
|
458
|
+
viewAttributes,
|
|
459
|
+
layoutContext,
|
|
460
|
+
getAttributeValue,
|
|
461
|
+
handleAttributeChange,
|
|
462
|
+
handleChildrenChange,
|
|
463
|
+
tabs,
|
|
464
|
+
tabContentInfo,
|
|
465
|
+
firstAvailableTab,
|
|
466
|
+
activeTab,
|
|
467
|
+
setActiveTab,
|
|
468
|
+
activeEntries,
|
|
469
|
+
specialSectionsByTab,
|
|
470
|
+
activeSpecialSections,
|
|
471
|
+
mockableFeatureKeys,
|
|
472
|
+
activeMockableFeature,
|
|
473
|
+
setActiveMockableFeature,
|
|
474
|
+
hasStringChildren,
|
|
475
|
+
childrenValue,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
@@ -7,7 +7,7 @@ import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
|
|
|
7
7
|
import { useMergedStyle } from '../../utils/useMergedStyle';
|
|
8
8
|
import { Icon } from '../../components/Icon.generated';
|
|
9
9
|
import { IconsType } from '../../types/Icons';
|
|
10
|
-
import { useExtractTextStyle } from '../../
|
|
10
|
+
import { useExtractTextStyle } from '../../attribute-analyser/style/web/useExtractTextStyle';
|
|
11
11
|
|
|
12
12
|
function BIcon({ node }: BIconComponentProps) {
|
|
13
13
|
useLogRender('BIcon');
|
|
@@ -28,9 +28,9 @@ function BIcon({ node }: BIconComponentProps) {
|
|
|
28
28
|
baseStyle,
|
|
29
29
|
isSelected ? SELECTED_OUTLINE_STYLE : undefined,
|
|
30
30
|
);
|
|
31
|
-
const
|
|
32
|
-
const iconType =
|
|
33
|
-
const strokeWidth =
|
|
31
|
+
const attrs = node.attributes;
|
|
32
|
+
const iconType = attrs?.iconType ?? 'activity';
|
|
33
|
+
const strokeWidth = attrs?.strokeWidth;
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<Icon
|
|
@@ -25,9 +25,6 @@ export interface BIconStyleGenerated {
|
|
|
25
25
|
fontFamily?: string;
|
|
26
26
|
fontWeight?: string;
|
|
27
27
|
textAlign?: TextAlignOptionType;
|
|
28
|
-
adjustsFontSizeToFit?: boolean;
|
|
29
|
-
showEllipsis?: boolean;
|
|
30
|
-
scrollable?: boolean;
|
|
31
28
|
flexDirection?: FlexDirectionOptionType;
|
|
32
29
|
alignItems?: AlignItemsOptionType;
|
|
33
30
|
justifyContent?: JustifyContentOptionType;
|
|
@@ -61,15 +58,18 @@ export interface BIconStyleGenerated {
|
|
|
61
58
|
left?: string;
|
|
62
59
|
right?: string;
|
|
63
60
|
zIndex?: number;
|
|
64
|
-
iconType?: string;
|
|
65
|
-
size?: number;
|
|
66
|
-
strokeWidth?: number;
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
export interface BIconPropsGenerated {
|
|
70
64
|
child: string;
|
|
71
65
|
attributes: {
|
|
72
66
|
style?: BIconStyleGenerated;
|
|
67
|
+
adjustsFontSizeToFit?: boolean;
|
|
68
|
+
showEllipsis?: boolean;
|
|
69
|
+
scrollable?: boolean;
|
|
70
|
+
iconType?: string;
|
|
71
|
+
size?: number;
|
|
72
|
+
strokeWidth?: number;
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -11,12 +11,11 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"meta": {
|
|
14
|
-
"desiredParent": [
|
|
15
|
-
"all"
|
|
16
|
-
],
|
|
14
|
+
"desiredParent": ["all"],
|
|
17
15
|
"label": "BIcon",
|
|
18
16
|
"description": "Renders an icon from the icon set.",
|
|
19
|
-
"styles": {
|
|
17
|
+
"styles": {},
|
|
18
|
+
"attributes": {
|
|
20
19
|
"iconType": {
|
|
21
20
|
"label": "Icon",
|
|
22
21
|
"description": "Which icon to render.",
|
|
@@ -27,14 +26,14 @@
|
|
|
27
26
|
"size": {
|
|
28
27
|
"label": "Size",
|
|
29
28
|
"description": "Icon size (px).",
|
|
30
|
-
"category": "
|
|
29
|
+
"category": "other",
|
|
31
30
|
"specialCategory": null,
|
|
32
31
|
"sort": 2
|
|
33
32
|
},
|
|
34
33
|
"strokeWidth": {
|
|
35
34
|
"label": "Stroke Width",
|
|
36
35
|
"description": "SVG stroke width override (applied to the icon paths).",
|
|
37
|
-
"category": "
|
|
36
|
+
"category": "other",
|
|
38
37
|
"specialCategory": null,
|
|
39
38
|
"sort": 3
|
|
40
39
|
}
|
|
@@ -2,7 +2,7 @@ import React, { useId, useMemo } from 'react';
|
|
|
2
2
|
import type { BackgroundImageComponentProps } from './BackgroundImageProps.generated';
|
|
3
3
|
import useNode from '../useNode';
|
|
4
4
|
import { useBuilderParams } from '../../components/BuilderProvider';
|
|
5
|
-
import { useExtractViewStyle } from '../../
|
|
5
|
+
import { useExtractViewStyle } from '../../attribute-analyser/style/web/useExtractViewStyle';
|
|
6
6
|
import { resolveImageSrc } from '../../utils/getImage';
|
|
7
7
|
import { useLogRender } from '../../utils/useLogRender';
|
|
8
8
|
import { isNodeSelected, SELECTED_OUTLINE_STYLE } from '../../utils/selection';
|
|
@@ -32,7 +32,8 @@ function BackgroundImage({ node }: BackgroundImageComponentProps) {
|
|
|
32
32
|
if (resolved) style.backgroundImage = `url(${resolved})`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const resizeMode = attrs?.resizeMode ?? (styleBag as any)?.resizeMode;
|
|
36
|
+
switch (resizeMode) {
|
|
36
37
|
case 'cover':
|
|
37
38
|
style.backgroundSize = 'cover';
|
|
38
39
|
break;
|