@developer_tribe/react-builder 1.2.22 → 1.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +6 -6
- package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +6 -4
- package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +5 -3
- package/dist/build-components/Image/ImageProps.generated.d.ts +2 -4
- package/dist/build-components/NavigationBarColor/NavigationBarColor.d.ts +5 -0
- package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +54 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -3
- package/dist/build-components/Separator/Separator.d.ts +5 -0
- package/dist/build-components/Separator/SeparatorProps.generated.d.ts +21 -0
- package/dist/build-components/StatusBarColor/StatusBarColor.d.ts +5 -0
- package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +54 -0
- package/dist/build-components/index.d.ts +4 -1
- package/dist/build-components/patterns.generated.d.ts +2111 -1251
- package/dist/components/AttributesEditorPanel.d.ts +1 -1
- package/dist/components/BuilderProvider.d.ts +1 -1
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +6 -6
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +4 -4
- package/dist/index.web.esm.js.map +1 -1
- package/dist/store.d.ts +4 -0
- package/dist/styles.css +1 -1
- package/dist/utils/attributeStyle.d.ts +21 -0
- package/dist/utils/extractImageStyle.d.ts +1 -1
- package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +1 -1
- package/package.json +7 -2
- package/src/DeviceMockFrame.tsx +8 -2
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/paywall-1.json +44 -39
- package/src/assets/samples/paywall-2.json +44 -25
- package/src/assets/samples/paywall-app-delete-offer.json +40 -21
- package/src/assets/samples/paywall-app-open-offer.json +40 -21
- package/src/assets/samples/paywall-back-offer.json +40 -21
- package/src/assets/samples/paywall-notification-offer.json +40 -21
- package/src/assets/samples/vpn-onboard-1.json +84 -39
- package/src/assets/samples/vpn-onboard-2.json +85 -40
- package/src/assets/samples/vpn-onboard-3.json +84 -39
- package/src/assets/samples/vpn-onboard-4.json +84 -39
- package/src/assets/samples/vpn-onboard-5.json +102 -55
- package/src/assets/samples/vpn-onboard-6.json +87 -38
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +31 -25
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +26 -11
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +21 -11
- package/src/attributes-editor/useAttributesEditorModel.ts +23 -17
- package/src/build-components/BackgroundImage/pattern.json +9 -7
- package/src/build-components/CarouselDots/CarouselDots.tsx +12 -11
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +3 -1
- package/src/build-components/Image/ImageProps.generated.ts +2 -4
- package/src/build-components/Image/pattern.json +15 -25
- package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +39 -0
- package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +71 -0
- package/src/build-components/NavigationBarColor/pattern.json +34 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +19 -5
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +8 -10
- package/src/build-components/OnboardDot/OnboardDot.tsx +12 -10
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +15 -4
- package/src/build-components/OnboardImage/OnboardImage.tsx +1 -1
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -3
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +3 -1
- package/src/build-components/RenderNode.generated.tsx +15 -0
- package/src/build-components/Separator/Separator.tsx +41 -0
- package/src/build-components/Separator/SeparatorProps.generated.ts +26 -0
- package/src/build-components/Separator/pattern.json +59 -0
- package/src/build-components/StatusBarColor/StatusBarColor.tsx +39 -0
- package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +71 -0
- package/src/build-components/StatusBarColor/pattern.json +34 -0
- package/src/build-components/Text/pattern.json +45 -38
- package/src/build-components/index.ts +15 -0
- package/src/build-components/patterns.generated.ts +2153 -1272
- package/src/build-components/useNode.ts +24 -25
- package/src/components/AttributesEditorPanel.tsx +4 -5
- package/src/components/Builder.tsx +1 -2
- package/src/components/BuilderProvider.tsx +43 -6
- package/src/components/JsonTextEditor.tsx +2 -2
- package/src/components/LoadingComponent.tsx +1 -1
- package/src/components/RenderErrorBoundary.tsx +1 -3
- package/src/index.ts +3 -0
- package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +3 -3
- package/src/modals/BenefitPresetsModal.tsx +1 -1
- package/src/modals/ProductPresetsModal.tsx +1 -1
- package/src/pages/DebugJsonPage.tsx +7 -4
- package/src/pages/ProjectDebug.tsx +1 -1
- package/src/pages/ProjectPage.tsx +31 -32
- package/src/pages/ProjectValidationPage.tsx +2 -2
- package/src/store.ts +13 -0
- package/src/styles/layout/_builder.scss +6 -0
- package/src/utils/__special_exceptions.ts +5 -5
- package/src/utils/analyseNode.ts +2 -2
- package/src/utils/analyseNodeByPatterns.ts +10 -9
- package/src/utils/analyseNodeStructural.ts +1 -1
- package/src/utils/attributeStyle.ts +104 -0
- package/src/utils/extractImageStyle.ts +17 -13
- package/src/utils/extractTextStyle/extractTextStyle.ts +7 -7
- package/src/utils/extractTextStyle/extractTextStyleNative.ts +10 -10
- package/src/utils/extractViewStyle/extractViewStyle.ts +8 -11
- package/src/utils/extractViewStyle/extractViewStyleNative.ts +19 -19
- package/src/utils/loadFontFamily.ts +14 -19
- package/src/utils/logRenderStore.ts +5 -4
- package/src/utils/nodeTree.ts +1 -1
- package/src/utils/patterns.ts +26 -31
- package/src/utils/repairNodeKeys.ts +5 -7
- package/src/utils/wrapNodeInMain.ts +3 -3
|
@@ -70,7 +70,7 @@ function isNodeDataLike(
|
|
|
70
70
|
value: unknown,
|
|
71
71
|
): value is NodeData<NodeDefaultAttribute> {
|
|
72
72
|
if (!isPlainObject(value)) return false;
|
|
73
|
-
const maybeType =
|
|
73
|
+
const maybeType = value.type;
|
|
74
74
|
// `children` is optional in persisted JSON; treat missing as `undefined`.
|
|
75
75
|
// The structural validator handles overall shape; the pattern validator should
|
|
76
76
|
// focus on types/attributes/children rules, not require the key to exist.
|
|
@@ -316,9 +316,10 @@ function validateCustomObjectValue(
|
|
|
316
316
|
return fail(`Expected object for type "${typeName}"`, path);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
const valueRecord = value as Record<string, unknown>;
|
|
319
320
|
for (const [fieldName, fieldSpec] of Object.entries(schema)) {
|
|
320
|
-
if (!(fieldName in
|
|
321
|
-
const fieldValue =
|
|
321
|
+
if (!(fieldName in valueRecord)) continue;
|
|
322
|
+
const fieldValue = valueRecord[fieldName];
|
|
322
323
|
const fieldPath = joinPath(path, fieldName);
|
|
323
324
|
const res = validateAttributeValue(
|
|
324
325
|
componentType,
|
|
@@ -387,8 +388,9 @@ function validateAttributesByPattern(
|
|
|
387
388
|
{}) as AttributeSchema;
|
|
388
389
|
const styleSchema = getStyleSubSchema(schema);
|
|
389
390
|
|
|
390
|
-
const
|
|
391
|
-
const
|
|
391
|
+
const attrsRecord = attrs as Record<string, unknown>;
|
|
392
|
+
const maybeStyle = attrsRecord.style;
|
|
393
|
+
const maybeStyles = attrsRecord.styles;
|
|
392
394
|
|
|
393
395
|
// schemaVersion=2 requires `attributes.styles` (plural), not `attributes.style` (singular)
|
|
394
396
|
if (maybeStyle != null) {
|
|
@@ -403,9 +405,8 @@ function validateAttributesByPattern(
|
|
|
403
405
|
if (!isPlainObject(maybeStyles)) {
|
|
404
406
|
return fail(`styles must be an object`, joinPath(path, 'styles'));
|
|
405
407
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
)) {
|
|
408
|
+
const stylesRecord = maybeStyles as Record<string, unknown>;
|
|
409
|
+
for (const [styleKey, styleValue] of Object.entries(stylesRecord)) {
|
|
409
410
|
const spec = (styleSchema?.[styleKey] ?? schema?.[styleKey]) as
|
|
410
411
|
| AttributeTypeSpec
|
|
411
412
|
| undefined;
|
|
@@ -510,7 +511,7 @@ function validateAnyNodeByPatterns(
|
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
if (isNodeArray(node)) {
|
|
513
|
-
const arr = node as
|
|
514
|
+
const arr = node as Node<NodeDefaultAttribute>[];
|
|
514
515
|
for (let i = 0; i < arr.length; i++) {
|
|
515
516
|
const res = validateAnyNodeByPatterns(arr[i], `${path}[${i}]`);
|
|
516
517
|
if (!res.valid) return res;
|
|
@@ -28,7 +28,7 @@ export function analyseNodeStructural(
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (isNodeArray(node)) {
|
|
31
|
-
const nodeArray = node as
|
|
31
|
+
const nodeArray = node as Node[];
|
|
32
32
|
for (const value of nodeArray) {
|
|
33
33
|
const res = analyseNodeStructural(value as Node<NodeDefaultAttribute>);
|
|
34
34
|
if (!res.valid) return res;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for attribute/style bag access.
|
|
3
|
+
* schemaVersion=2 uses `attributes.styles`, legacy uses `attributes.style`.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { NodeDefaultAttribute } from '../types/Node';
|
|
7
|
+
|
|
8
|
+
/** Returns styles or style bag from attributes (schemaVersion=2 uses styles, legacy uses style). */
|
|
9
|
+
export function getStyleBag(
|
|
10
|
+
attributes: NodeDefaultAttribute | undefined,
|
|
11
|
+
): Record<string, unknown> | undefined {
|
|
12
|
+
const record = toAttributeRecord(attributes);
|
|
13
|
+
const bag = record.styles ?? record.style;
|
|
14
|
+
return isPlainObject(bag) ? (bag as Record<string, unknown>) : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Safe indexed access to attributes. Use for reading style/direct props. */
|
|
18
|
+
export function toAttributeRecord(
|
|
19
|
+
attributes: unknown,
|
|
20
|
+
): Record<string, unknown> {
|
|
21
|
+
return (attributes ?? {}) as Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Style-key filtering – separates visual style keys from non-style attributes
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* All attribute keys that represent visual style properties.
|
|
34
|
+
* Used to separate style keys from non-style (behavioral/content) keys.
|
|
35
|
+
*
|
|
36
|
+
* Keep in sync with ViewStyleGenerated, TextStyleGenerated, and ImageStyleGenerated.
|
|
37
|
+
*/
|
|
38
|
+
const STYLE_ATTR_KEYS_LIST = [
|
|
39
|
+
// Style bag containers
|
|
40
|
+
'style',
|
|
41
|
+
'styles',
|
|
42
|
+
// Layout
|
|
43
|
+
'flexDirection',
|
|
44
|
+
'flexWrap',
|
|
45
|
+
'alignItems',
|
|
46
|
+
'justifyContent',
|
|
47
|
+
'gap',
|
|
48
|
+
// Padding
|
|
49
|
+
'padding',
|
|
50
|
+
'paddingHorizontal',
|
|
51
|
+
'paddingVertical',
|
|
52
|
+
'paddingTop',
|
|
53
|
+
'paddingBottom',
|
|
54
|
+
'paddingLeft',
|
|
55
|
+
'paddingRight',
|
|
56
|
+
// Margin
|
|
57
|
+
'margin',
|
|
58
|
+
'marginHorizontal',
|
|
59
|
+
'marginVertical',
|
|
60
|
+
'marginTop',
|
|
61
|
+
'marginBottom',
|
|
62
|
+
'marginLeft',
|
|
63
|
+
'marginRight',
|
|
64
|
+
// Background & border
|
|
65
|
+
'backgroundColor',
|
|
66
|
+
'borderRadius',
|
|
67
|
+
// Sizing
|
|
68
|
+
'width',
|
|
69
|
+
'minWidth',
|
|
70
|
+
'maxWidth',
|
|
71
|
+
'height',
|
|
72
|
+
'minHeight',
|
|
73
|
+
'maxHeight',
|
|
74
|
+
// Flex & position
|
|
75
|
+
'flex',
|
|
76
|
+
'position',
|
|
77
|
+
'top',
|
|
78
|
+
'bottom',
|
|
79
|
+
'left',
|
|
80
|
+
'right',
|
|
81
|
+
'zIndex',
|
|
82
|
+
// Text
|
|
83
|
+
'color',
|
|
84
|
+
'fontSize',
|
|
85
|
+
'fontFamily',
|
|
86
|
+
'fontWeight',
|
|
87
|
+
'textAlign',
|
|
88
|
+
// Image
|
|
89
|
+
'resizeMode',
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
92
|
+
/** Type-level union of all style attribute keys. Use with `Omit<T, StyleAttrKey>`. */
|
|
93
|
+
export type StyleAttrKey = (typeof STYLE_ATTR_KEYS_LIST)[number];
|
|
94
|
+
|
|
95
|
+
const STYLE_ATTR_KEYS: ReadonlySet<string> = new Set(STYLE_ATTR_KEYS_LIST);
|
|
96
|
+
|
|
97
|
+
/** Strips all visual-style keys from an attributes record, returning only non-style keys. */
|
|
98
|
+
export function stripStyleKeys(
|
|
99
|
+
attrs: Record<string, unknown>,
|
|
100
|
+
): Record<string, unknown> {
|
|
101
|
+
return Object.fromEntries(
|
|
102
|
+
Object.entries(attrs).filter(([key]) => !STYLE_ATTR_KEYS.has(key)),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ImagePropsGenerated,
|
|
3
|
+
type ResizeModeOptionType,
|
|
4
|
+
} from '../build-components/Image/ImageProps.generated';
|
|
2
5
|
import type { NodeData } from '../types/Node';
|
|
3
6
|
import {
|
|
4
7
|
extractViewStyle,
|
|
5
8
|
extractViewStyleNative,
|
|
6
9
|
ExtractViewStyleOptions,
|
|
7
10
|
} from './extractViewStyle';
|
|
11
|
+
import { getStyleBag } from './attributeStyle';
|
|
8
12
|
|
|
9
13
|
export function extractImageStyle<T extends ImagePropsGenerated['attributes']>(
|
|
10
14
|
node: NodeData<T>,
|
|
11
15
|
options: ExtractViewStyleOptions = {},
|
|
12
16
|
) {
|
|
13
17
|
const attributes = node.attributes;
|
|
14
|
-
const styleBag = (attributes as any)?.style as
|
|
15
|
-
| Record<string, unknown>
|
|
16
|
-
| undefined;
|
|
17
18
|
const style: React.CSSProperties = {};
|
|
18
19
|
|
|
19
20
|
if (!attributes) return style;
|
|
20
21
|
|
|
21
|
-
// Map resizeMode to CSS object-fit
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
// Map resizeMode to CSS object-fit (schemaVersion=2 uses `styles`, fallback to legacy `style`)
|
|
23
|
+
const stylesBag = getStyleBag(attributes) as
|
|
24
|
+
| ImagePropsGenerated['attributes']['style']
|
|
25
|
+
| undefined;
|
|
26
|
+
const resizeMode: ResizeModeOptionType | undefined = stylesBag?.resizeMode;
|
|
24
27
|
if (resizeMode === 'cover') style.objectFit = 'cover';
|
|
25
28
|
else if (resizeMode === 'contain') style.objectFit = 'contain';
|
|
26
29
|
else if (resizeMode === 'stretch') style.objectFit = 'fill';
|
|
@@ -33,18 +36,19 @@ export function extractImageStyleNative<
|
|
|
33
36
|
T extends ImagePropsGenerated['attributes'],
|
|
34
37
|
>(node: NodeData<T>, options: ExtractViewStyleOptions = {}) {
|
|
35
38
|
const attributes = node.attributes;
|
|
36
|
-
const styleBag = (attributes as any)?.style as
|
|
37
|
-
| Record<string, unknown>
|
|
38
|
-
| undefined;
|
|
39
39
|
if (!attributes) return {};
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// schemaVersion=2 uses `styles`, fallback to legacy `style`
|
|
42
|
+
const nativeStylesBag = getStyleBag(attributes) as
|
|
43
|
+
| ImagePropsGenerated['attributes']['style']
|
|
44
|
+
| undefined;
|
|
45
|
+
const resizeMode: ResizeModeOptionType | undefined =
|
|
46
|
+
nativeStylesBag?.resizeMode;
|
|
43
47
|
|
|
44
48
|
// In RN, resizeMode is usually an Image prop, but many codebases also accept it on ImageStyle.
|
|
45
49
|
// We return it here so consumers can pass it through as they prefer.
|
|
46
50
|
const nativeStyle: Record<string, unknown> = {};
|
|
47
51
|
if (resizeMode) nativeStyle.resizeMode = resizeMode;
|
|
48
52
|
|
|
49
|
-
return { ...extractViewStyleNative(node, options
|
|
53
|
+
return { ...extractViewStyleNative(node, options), ...nativeStyle };
|
|
50
54
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
resolveClosestFontWeightKey,
|
|
18
18
|
} from '../loadFontFamily';
|
|
19
19
|
import { fontsDebug } from '../fontsDebug';
|
|
20
|
+
import { getStyleBag, toAttributeRecord } from '../attributeStyle';
|
|
20
21
|
|
|
21
22
|
const inFlightFontLoads: Map<string, Promise<void>> = new Map<
|
|
22
23
|
string,
|
|
@@ -97,11 +98,10 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
|
|
|
97
98
|
options: ExtractTextStyleOptions = {},
|
|
98
99
|
) {
|
|
99
100
|
const attributes = node.attributes;
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
| undefined;
|
|
101
|
+
const attrRecord = toAttributeRecord(attributes);
|
|
102
|
+
const styleBag = getStyleBag(attributes) as TextStyleGenerated | undefined;
|
|
103
103
|
const get = (key: string): unknown => {
|
|
104
|
-
const direct =
|
|
104
|
+
const direct = attrRecord[key];
|
|
105
105
|
if (direct !== undefined && direct !== null) return direct;
|
|
106
106
|
return styleBag?.[key as keyof TextStyleGenerated];
|
|
107
107
|
};
|
|
@@ -199,15 +199,15 @@ export function extractTextStyle<T extends TextPropsGenerated['attributes']>(
|
|
|
199
199
|
'textTransform',
|
|
200
200
|
]);
|
|
201
201
|
|
|
202
|
-
const textStyle:
|
|
202
|
+
const textStyle: Record<string, unknown> = {};
|
|
203
203
|
for (const key in fullStyle) {
|
|
204
204
|
const typedKey = key as keyof React.CSSProperties;
|
|
205
205
|
if (textStyleProperties.has(typedKey)) {
|
|
206
|
-
|
|
206
|
+
textStyle[key] = fullStyle[typedKey];
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
return textStyle;
|
|
210
|
+
return textStyle as React.CSSProperties;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
return fullStyle;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
findFontDefinition,
|
|
13
13
|
resolveClosestFontWeightKey,
|
|
14
14
|
} from '../loadFontFamily';
|
|
15
|
+
import { getStyleBag, toAttributeRecord } from '../attributeStyle';
|
|
15
16
|
|
|
16
17
|
export type ExtractTextStyleNativeOptions = {
|
|
17
18
|
appConfig?: AppConfig;
|
|
@@ -37,11 +38,10 @@ export function extractTextStyleNative<
|
|
|
37
38
|
T extends TextPropsGenerated['attributes'],
|
|
38
39
|
>(node: NodeData<T>, options: ExtractTextStyleNativeOptions = {}) {
|
|
39
40
|
const attributes = node.attributes;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
| undefined;
|
|
41
|
+
const attrRecord = toAttributeRecord(attributes);
|
|
42
|
+
const styleBag = getStyleBag(attributes);
|
|
43
43
|
const get = (key: string): unknown => {
|
|
44
|
-
const direct =
|
|
44
|
+
const direct = attrRecord[key];
|
|
45
45
|
if (direct !== undefined && direct !== null) return direct;
|
|
46
46
|
return styleBag?.[key];
|
|
47
47
|
};
|
|
@@ -58,13 +58,13 @@ export function extractTextStyleNative<
|
|
|
58
58
|
return style;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const rawFontSize = get('fontSize') as
|
|
61
|
+
const rawFontSize = get('fontSize') as string | number | undefined;
|
|
62
62
|
const parsedFontSize = parseSize(rawFontSize);
|
|
63
63
|
if (typeof parsedFontSize === 'number') style.fontSize = parsedFontSize;
|
|
64
64
|
else style.fontSize = fs(14);
|
|
65
65
|
|
|
66
|
-
const fontFamily = get('fontFamily') as
|
|
67
|
-
const fontWeight = get('fontWeight') as
|
|
66
|
+
const fontFamily = get('fontFamily') as string | undefined;
|
|
67
|
+
const fontWeight = get('fontWeight') as string | number | undefined;
|
|
68
68
|
const requestedWeight = weightToNumericKey(fontWeight);
|
|
69
69
|
const normalizedFontFamily =
|
|
70
70
|
typeof fontFamily === 'string' && fontFamily.trim().length > 0
|
|
@@ -89,14 +89,14 @@ export function extractTextStyleNative<
|
|
|
89
89
|
if (!normalizedFontFamily && normalizedFontWeight)
|
|
90
90
|
style.fontWeight = normalizedFontWeight;
|
|
91
91
|
|
|
92
|
-
const resolvedTextColor = parseColor(get('color') as
|
|
92
|
+
const resolvedTextColor = parseColor(get('color') as string | undefined, {
|
|
93
93
|
projectColors: options.projectColors,
|
|
94
94
|
theme,
|
|
95
95
|
});
|
|
96
96
|
style.color = resolvedTextColor ?? fallbackColor;
|
|
97
97
|
|
|
98
|
-
const textAlign = get('textAlign');
|
|
99
|
-
if (textAlign) style.textAlign = textAlign
|
|
98
|
+
const textAlign = get('textAlign') as string | undefined;
|
|
99
|
+
if (textAlign) style.textAlign = textAlign;
|
|
100
100
|
|
|
101
101
|
const viewStyle = extractViewStyleNative(node, {
|
|
102
102
|
projectColors: options.projectColors,
|
|
@@ -6,6 +6,7 @@ import type { NodeData } from '../../types/Node';
|
|
|
6
6
|
import type { ProjectColors } from '../../types/Project';
|
|
7
7
|
import { parseSize } from '../../size-matters';
|
|
8
8
|
import { parseColor } from '../parseColor';
|
|
9
|
+
import { getStyleBag, toAttributeRecord } from '../attributeStyle';
|
|
9
10
|
|
|
10
11
|
export type ExtractViewStyleOptions = {
|
|
11
12
|
projectColors?: ProjectColors;
|
|
@@ -17,11 +18,10 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
17
18
|
options: ExtractViewStyleOptions = {},
|
|
18
19
|
) {
|
|
19
20
|
const attributes = node.attributes;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
| undefined;
|
|
21
|
+
const attrRecord = toAttributeRecord(attributes);
|
|
22
|
+
const styleBag = getStyleBag(attributes) as ViewStyleGenerated | undefined;
|
|
23
23
|
const get = (key: string): unknown => {
|
|
24
|
-
const direct =
|
|
24
|
+
const direct = attrRecord[key];
|
|
25
25
|
if (direct !== undefined && direct !== null) return direct;
|
|
26
26
|
return styleBag?.[key as keyof ViewStyleGenerated];
|
|
27
27
|
};
|
|
@@ -112,10 +112,7 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
112
112
|
);
|
|
113
113
|
|
|
114
114
|
const marginHorizontalRaw =
|
|
115
|
-
(
|
|
116
|
-
| string
|
|
117
|
-
| number
|
|
118
|
-
| undefined) ??
|
|
115
|
+
(attrRecord.marginHorizontal as string | number | undefined) ??
|
|
119
116
|
(styleBag?.marginHorizontal as string | number | undefined);
|
|
120
117
|
if (!isEmptySizeValue(marginHorizontalRaw)) {
|
|
121
118
|
const parsed = parseSize(marginHorizontalRaw);
|
|
@@ -209,13 +206,13 @@ export function extractViewStyle<T extends ViewPropsGenerated['attributes']>(
|
|
|
209
206
|
'overflowY',
|
|
210
207
|
]);
|
|
211
208
|
|
|
212
|
-
const filteredStyle:
|
|
209
|
+
const filteredStyle: Record<string, unknown> = {};
|
|
213
210
|
for (const key in style) {
|
|
214
211
|
const typedKey = key as keyof React.CSSProperties;
|
|
215
212
|
if (viewStyleProperties.has(typedKey)) {
|
|
216
|
-
|
|
213
|
+
filteredStyle[key] = style[typedKey];
|
|
217
214
|
}
|
|
218
215
|
}
|
|
219
216
|
|
|
220
|
-
return filteredStyle;
|
|
217
|
+
return filteredStyle as React.CSSProperties;
|
|
221
218
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ViewPropsGenerated } from '../../build-components/View/ViewProps.generated';
|
|
1
|
+
import type { ViewPropsGenerated } from '../../build-components/View/ViewProps.generated';
|
|
2
2
|
import type { NodeData } from '../../types/Node';
|
|
3
3
|
import type { ProjectColors } from '../../types/Project';
|
|
4
4
|
import { parseSize } from '../../size-matters';
|
|
5
5
|
import { parseColor } from '../parseColor';
|
|
6
|
+
import { getStyleBag, toAttributeRecord } from '../attributeStyle';
|
|
6
7
|
|
|
7
8
|
export type ExtractViewStyleNativeOptions = {
|
|
8
9
|
projectColors?: ProjectColors;
|
|
@@ -17,11 +18,10 @@ export function extractViewStyleNative<
|
|
|
17
18
|
T extends ViewPropsGenerated['attributes'],
|
|
18
19
|
>(node: NodeData<T>, options: ExtractViewStyleNativeOptions = {}) {
|
|
19
20
|
const attributes = node.attributes;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
| undefined;
|
|
21
|
+
const attrRecord = toAttributeRecord(attributes);
|
|
22
|
+
const styleBag = getStyleBag(attributes);
|
|
23
23
|
const get = (key: string): unknown => {
|
|
24
|
-
const direct =
|
|
24
|
+
const direct = attrRecord[key];
|
|
25
25
|
if (direct !== undefined && direct !== null) return direct;
|
|
26
26
|
return styleBag?.[key];
|
|
27
27
|
};
|
|
@@ -41,18 +41,18 @@ export function extractViewStyleNative<
|
|
|
41
41
|
// We intentionally do not map it to overflowX/Y.
|
|
42
42
|
|
|
43
43
|
const flexDirection = get('flexDirection');
|
|
44
|
-
if (flexDirection) style.flexDirection = flexDirection
|
|
44
|
+
if (flexDirection) style.flexDirection = String(flexDirection);
|
|
45
45
|
const alignItems = get('alignItems');
|
|
46
|
-
if (alignItems) style.alignItems = alignItems
|
|
46
|
+
if (alignItems) style.alignItems = String(alignItems);
|
|
47
47
|
const justifyContent = get('justifyContent');
|
|
48
|
-
if (justifyContent) style.justifyContent = justifyContent
|
|
48
|
+
if (justifyContent) style.justifyContent = String(justifyContent);
|
|
49
49
|
|
|
50
50
|
const setParsedSize = (property: string, rawValue: unknown) => {
|
|
51
51
|
if (isEmptySizeValue(rawValue)) return;
|
|
52
|
-
const parsed = parseSize(rawValue as
|
|
52
|
+
const parsed = parseSize(rawValue as string | number);
|
|
53
53
|
// RN generally expects numbers (dp). We allow percentages for width/height-like props.
|
|
54
54
|
if (typeof parsed === 'number' || typeof parsed === 'string') {
|
|
55
|
-
|
|
55
|
+
style[property] = parsed;
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ export function extractViewStyleNative<
|
|
|
68
68
|
setParsedSize('margin', get('margin'));
|
|
69
69
|
setParsedSize(
|
|
70
70
|
'marginHorizontal',
|
|
71
|
-
|
|
71
|
+
attrRecord.marginHorizontal ?? styleBag?.marginHorizontal,
|
|
72
72
|
);
|
|
73
73
|
setParsedSize('marginVertical', get('marginVertical'));
|
|
74
74
|
setParsedSize('marginTop', get('marginTop'));
|
|
@@ -76,9 +76,9 @@ export function extractViewStyleNative<
|
|
|
76
76
|
setParsedSize('marginLeft', get('marginLeft'));
|
|
77
77
|
setParsedSize('marginRight', get('marginRight'));
|
|
78
78
|
|
|
79
|
-
const backgroundColor = get('backgroundColor') as
|
|
79
|
+
const backgroundColor = get('backgroundColor') as string | undefined;
|
|
80
80
|
if (backgroundColor) {
|
|
81
|
-
|
|
81
|
+
style.backgroundColor =
|
|
82
82
|
parseColor(backgroundColor, {
|
|
83
83
|
projectColors: options.projectColors,
|
|
84
84
|
theme: options.theme,
|
|
@@ -93,19 +93,19 @@ export function extractViewStyleNative<
|
|
|
93
93
|
setParsedSize('minHeight', get('minHeight'));
|
|
94
94
|
setParsedSize('maxHeight', get('maxHeight'));
|
|
95
95
|
|
|
96
|
-
const flex = get('flex') as
|
|
97
|
-
if (flex !== undefined)
|
|
96
|
+
const flex = get('flex') as number | undefined;
|
|
97
|
+
if (flex !== undefined) style.flex = flex;
|
|
98
98
|
|
|
99
|
-
const position = get('position') as
|
|
100
|
-
if (position)
|
|
99
|
+
const position = get('position') as string | undefined;
|
|
100
|
+
if (position) style.position = position;
|
|
101
101
|
|
|
102
102
|
setParsedSize('top', get('top'));
|
|
103
103
|
setParsedSize('bottom', get('bottom'));
|
|
104
104
|
setParsedSize('left', get('left'));
|
|
105
105
|
setParsedSize('right', get('right'));
|
|
106
106
|
|
|
107
|
-
const zIndex = get('zIndex') as
|
|
108
|
-
if (zIndex !== undefined)
|
|
107
|
+
const zIndex = get('zIndex') as number | undefined;
|
|
108
|
+
if (zIndex !== undefined) style.zIndex = zIndex;
|
|
109
109
|
|
|
110
110
|
return style;
|
|
111
111
|
}
|
|
@@ -124,25 +124,20 @@ export async function loadFontFamily(
|
|
|
124
124
|
// Not a browser environment (SSR / RN): do nothing.
|
|
125
125
|
if (typeof document === 'undefined') return;
|
|
126
126
|
|
|
127
|
-
const fontsApi =
|
|
128
|
-
if (!fontsApi || typeof
|
|
127
|
+
const fontsApi = document.fonts;
|
|
128
|
+
if (!fontsApi || typeof globalThis.FontFace !== 'function') {
|
|
129
129
|
throw new Error('Font loading is not supported in this environment');
|
|
130
130
|
}
|
|
131
|
-
const safeFontsApi = fontsApi as FontFaceSet;
|
|
132
131
|
|
|
133
132
|
function hasLoadedFace(familyName: string, weight: string): boolean {
|
|
134
133
|
try {
|
|
135
|
-
const
|
|
136
|
-
for (const face of set) {
|
|
137
|
-
const f = face as unknown as {
|
|
138
|
-
family?: string;
|
|
139
|
-
weight?: string;
|
|
140
|
-
status?: string;
|
|
141
|
-
};
|
|
134
|
+
for (const face of fontsApi) {
|
|
142
135
|
const fam =
|
|
143
|
-
typeof
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
typeof face.family === 'string'
|
|
137
|
+
? face.family.replace(/['"]/g, '')
|
|
138
|
+
: '';
|
|
139
|
+
const w = typeof face.weight === 'string' ? face.weight.trim() : '';
|
|
140
|
+
if (fam === familyName && w === weight && face.status === 'loaded') {
|
|
146
141
|
return true;
|
|
147
142
|
}
|
|
148
143
|
}
|
|
@@ -251,17 +246,17 @@ export async function loadFontFamily(
|
|
|
251
246
|
}
|
|
252
247
|
|
|
253
248
|
async function loadAndAdd(src: string | ArrayBuffer, label: string) {
|
|
254
|
-
const face = new
|
|
249
|
+
const face = new FontFace(name, src, {
|
|
255
250
|
weight: preferWeight,
|
|
256
251
|
display: 'swap',
|
|
257
|
-
})
|
|
252
|
+
});
|
|
258
253
|
fontsDebug.info('loadFontFamily: FontFace.load() begin', {
|
|
259
254
|
familyName: name,
|
|
260
255
|
weight: preferWeight,
|
|
261
256
|
source: label,
|
|
262
257
|
});
|
|
263
258
|
const loaded = await face.load();
|
|
264
|
-
|
|
259
|
+
fontsApi.add(loaded);
|
|
265
260
|
fontsDebug.info('loadFontFamily: loaded + added to document.fonts', {
|
|
266
261
|
familyName: name,
|
|
267
262
|
weight: preferWeight,
|
|
@@ -296,18 +291,18 @@ export async function loadFontFamily(
|
|
|
296
291
|
|
|
297
292
|
try {
|
|
298
293
|
const afterQuery = `${preferWeight} 16px "${name}"`;
|
|
299
|
-
const after =
|
|
294
|
+
const after = fontsApi.check(afterQuery);
|
|
300
295
|
fontsDebug.info('loadFontFamily: document.fonts.check after add', {
|
|
301
296
|
familyName: name,
|
|
302
297
|
weight: preferWeight,
|
|
303
298
|
result: after,
|
|
304
299
|
});
|
|
305
300
|
// Force the browser to "activate" the face for this query (and reveal errors if any).
|
|
306
|
-
if (typeof
|
|
301
|
+
if (typeof fontsApi.load === 'function') {
|
|
307
302
|
fontsDebug.info('loadFontFamily: document.fonts.load begin', {
|
|
308
303
|
query: afterQuery,
|
|
309
304
|
});
|
|
310
|
-
await
|
|
305
|
+
await fontsApi.load(afterQuery);
|
|
311
306
|
fontsDebug.info('loadFontFamily: document.fonts.load done', {
|
|
312
307
|
query: afterQuery,
|
|
313
308
|
});
|
|
@@ -67,20 +67,21 @@ export function logRenderStore(options: LogRenderStoreOptions = {}): void {
|
|
|
67
67
|
const { label, includeLocalStorage = true, extra } = options;
|
|
68
68
|
|
|
69
69
|
const state = useRenderStore.getState();
|
|
70
|
-
const
|
|
70
|
+
const stateRecord = state as Record<string, unknown>;
|
|
71
|
+
const keys = Object.keys(stateRecord).sort();
|
|
71
72
|
|
|
72
73
|
const snapshot: Record<string, unknown> = {};
|
|
73
74
|
const actions: string[] = [];
|
|
74
75
|
|
|
75
76
|
for (const key of keys) {
|
|
76
|
-
const v =
|
|
77
|
+
const v = stateRecord[key];
|
|
77
78
|
if (isFn(v)) actions.push(key);
|
|
78
79
|
else snapshot[key] = v;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
const title = `[RB Debug] Render store${label ? ` (${label})` : ''}`;
|
|
82
|
-
const groupCollapsed =
|
|
83
|
-
const groupEnd =
|
|
83
|
+
const groupCollapsed = console.groupCollapsed as unknown;
|
|
84
|
+
const groupEnd = console.groupEnd as unknown;
|
|
84
85
|
|
|
85
86
|
if (isFn(groupCollapsed)) groupCollapsed(title);
|
|
86
87
|
else {
|
package/src/utils/nodeTree.ts
CHANGED
|
@@ -19,7 +19,7 @@ export function deleteNodeFromTree(root: Node, target: Node): Node {
|
|
|
19
19
|
return changed ? nextChildren : root;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const data = root as
|
|
22
|
+
const data = root as NodeData;
|
|
23
23
|
if ('children' in data) {
|
|
24
24
|
const prev = data.children as Node;
|
|
25
25
|
if (!prev) return root;
|