@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.
Files changed (106) hide show
  1. package/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +6 -6
  2. package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +6 -4
  3. package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +5 -3
  4. package/dist/build-components/Image/ImageProps.generated.d.ts +2 -4
  5. package/dist/build-components/NavigationBarColor/NavigationBarColor.d.ts +5 -0
  6. package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +54 -0
  7. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -3
  8. package/dist/build-components/Separator/Separator.d.ts +5 -0
  9. package/dist/build-components/Separator/SeparatorProps.generated.d.ts +21 -0
  10. package/dist/build-components/StatusBarColor/StatusBarColor.d.ts +5 -0
  11. package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +54 -0
  12. package/dist/build-components/index.d.ts +4 -1
  13. package/dist/build-components/patterns.generated.d.ts +2111 -1251
  14. package/dist/components/AttributesEditorPanel.d.ts +1 -1
  15. package/dist/components/BuilderProvider.d.ts +1 -1
  16. package/dist/index.cjs.js +4 -4
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.esm.js +4 -4
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/index.web.cjs.js +6 -6
  22. package/dist/index.web.cjs.js.map +1 -1
  23. package/dist/index.web.esm.js +4 -4
  24. package/dist/index.web.esm.js.map +1 -1
  25. package/dist/store.d.ts +4 -0
  26. package/dist/styles.css +1 -1
  27. package/dist/utils/attributeStyle.d.ts +21 -0
  28. package/dist/utils/extractImageStyle.d.ts +1 -1
  29. package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +1 -1
  30. package/package.json +7 -2
  31. package/src/DeviceMockFrame.tsx +8 -2
  32. package/src/assets/meta.json +1 -1
  33. package/src/assets/samples/paywall-1.json +44 -39
  34. package/src/assets/samples/paywall-2.json +44 -25
  35. package/src/assets/samples/paywall-app-delete-offer.json +40 -21
  36. package/src/assets/samples/paywall-app-open-offer.json +40 -21
  37. package/src/assets/samples/paywall-back-offer.json +40 -21
  38. package/src/assets/samples/paywall-notification-offer.json +40 -21
  39. package/src/assets/samples/vpn-onboard-1.json +84 -39
  40. package/src/assets/samples/vpn-onboard-2.json +85 -40
  41. package/src/assets/samples/vpn-onboard-3.json +84 -39
  42. package/src/assets/samples/vpn-onboard-4.json +84 -39
  43. package/src/assets/samples/vpn-onboard-5.json +102 -55
  44. package/src/assets/samples/vpn-onboard-6.json +87 -38
  45. package/src/attribute-analyser/style/native/useExtractImageStyle.ts +31 -25
  46. package/src/attribute-analyser/style/native/useExtractTextStyle.ts +26 -11
  47. package/src/attribute-analyser/style/native/useExtractViewStyle.ts +21 -11
  48. package/src/attributes-editor/useAttributesEditorModel.ts +23 -17
  49. package/src/build-components/BackgroundImage/pattern.json +9 -7
  50. package/src/build-components/CarouselDots/CarouselDots.tsx +12 -11
  51. package/src/build-components/CarouselProvider/CarouselProvider.tsx +3 -1
  52. package/src/build-components/Image/ImageProps.generated.ts +2 -4
  53. package/src/build-components/Image/pattern.json +15 -25
  54. package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +39 -0
  55. package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +71 -0
  56. package/src/build-components/NavigationBarColor/pattern.json +34 -0
  57. package/src/build-components/OnboardButton/OnboardButton.tsx +19 -5
  58. package/src/build-components/OnboardButtons/OnboardButtons.tsx +8 -10
  59. package/src/build-components/OnboardDot/OnboardDot.tsx +12 -10
  60. package/src/build-components/OnboardFooter/OnboardFooter.tsx +15 -4
  61. package/src/build-components/OnboardImage/OnboardImage.tsx +1 -1
  62. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -3
  63. package/src/build-components/OnboardProvider/OnboardProvider.tsx +3 -1
  64. package/src/build-components/RenderNode.generated.tsx +15 -0
  65. package/src/build-components/Separator/Separator.tsx +41 -0
  66. package/src/build-components/Separator/SeparatorProps.generated.ts +26 -0
  67. package/src/build-components/Separator/pattern.json +59 -0
  68. package/src/build-components/StatusBarColor/StatusBarColor.tsx +39 -0
  69. package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +71 -0
  70. package/src/build-components/StatusBarColor/pattern.json +34 -0
  71. package/src/build-components/Text/pattern.json +45 -38
  72. package/src/build-components/index.ts +15 -0
  73. package/src/build-components/patterns.generated.ts +2153 -1272
  74. package/src/build-components/useNode.ts +24 -25
  75. package/src/components/AttributesEditorPanel.tsx +4 -5
  76. package/src/components/Builder.tsx +1 -2
  77. package/src/components/BuilderProvider.tsx +43 -6
  78. package/src/components/JsonTextEditor.tsx +2 -2
  79. package/src/components/LoadingComponent.tsx +1 -1
  80. package/src/components/RenderErrorBoundary.tsx +1 -3
  81. package/src/index.ts +3 -0
  82. package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +3 -3
  83. package/src/modals/BenefitPresetsModal.tsx +1 -1
  84. package/src/modals/ProductPresetsModal.tsx +1 -1
  85. package/src/pages/DebugJsonPage.tsx +7 -4
  86. package/src/pages/ProjectDebug.tsx +1 -1
  87. package/src/pages/ProjectPage.tsx +31 -32
  88. package/src/pages/ProjectValidationPage.tsx +2 -2
  89. package/src/store.ts +13 -0
  90. package/src/styles/layout/_builder.scss +6 -0
  91. package/src/utils/__special_exceptions.ts +5 -5
  92. package/src/utils/analyseNode.ts +2 -2
  93. package/src/utils/analyseNodeByPatterns.ts +10 -9
  94. package/src/utils/analyseNodeStructural.ts +1 -1
  95. package/src/utils/attributeStyle.ts +104 -0
  96. package/src/utils/extractImageStyle.ts +17 -13
  97. package/src/utils/extractTextStyle/extractTextStyle.ts +7 -7
  98. package/src/utils/extractTextStyle/extractTextStyleNative.ts +10 -10
  99. package/src/utils/extractViewStyle/extractViewStyle.ts +8 -11
  100. package/src/utils/extractViewStyle/extractViewStyleNative.ts +19 -19
  101. package/src/utils/loadFontFamily.ts +14 -19
  102. package/src/utils/logRenderStore.ts +5 -4
  103. package/src/utils/nodeTree.ts +1 -1
  104. package/src/utils/patterns.ts +26 -31
  105. package/src/utils/repairNodeKeys.ts +5 -7
  106. package/src/utils/wrapNodeInMain.ts +3 -3
@@ -1,26 +1,26 @@
1
1
  import { NodeData, NodeDefaultAttribute } from '../types/Node';
2
2
  import { getDefaultsForType } from '../utils/patterns';
3
3
 
4
+ type WithStyleBags = {
5
+ style?: Record<string, unknown>;
6
+ styles?: Record<string, unknown>;
7
+ };
8
+
4
9
  export default function useNode<
5
10
  T extends NodeDefaultAttribute = NodeDefaultAttribute,
6
11
  >(node: NodeData<T>): NodeData<T> {
7
12
  const type = node?.type;
8
13
  const defaults = getDefaultsForType(type) as Partial<T> | undefined;
9
14
  if (!defaults) return node;
10
- const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T & {
11
- style?: Record<string, unknown>;
12
- styles?: Record<string, unknown>;
13
- };
14
- const defaultAttributes = defaults as T as T & {
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)
15
+ const nodeAttributes = ((node.attributes as T) ?? ({} as T)) as T &
16
+ WithStyleBags;
17
+ const defaultAttributes = defaults as T & WithStyleBags;
18
+ // Merge styles from both defaults and node (schemaVersion=2 uses `styles` only).
19
+ // Read legacy `style` for back-compat with old persisted data, but output only `styles`.
19
20
  const defaultStyle = {
20
- ...(defaultAttributes?.styles ?? {}),
21
21
  ...(defaultAttributes?.style ?? {}),
22
+ ...(defaultAttributes?.styles ?? {}),
22
23
  };
23
- // Merge node style from both node.attributes.style and node.attributes.styles (preferring styles)
24
24
  const nodeStyle = {
25
25
  ...(nodeAttributes?.style ?? {}),
26
26
  ...(nodeAttributes?.styles ?? {}),
@@ -29,22 +29,21 @@ export default function useNode<
29
29
  ...defaultStyle,
30
30
  ...nodeStyle,
31
31
  };
32
- const mergedAttributes: T = {
33
- ...(defaultAttributes as T),
34
- ...(nodeAttributes as T),
35
- // Deep merge `style` so default style values aren't lost when the node provides partial style overrides.
36
- // Keep both `style` (for runtime back-compat) and `styles` (for editor schemaVersion=2) in sync.
37
- style: mergedStyle,
32
+ const mergedRecord: Record<string, unknown> = {
33
+ ...(defaultAttributes as Record<string, unknown>),
34
+ ...(nodeAttributes as Record<string, unknown>),
35
+ // Deep merge styles so default style values aren't lost when the node provides partial overrides.
36
+ // Only use `styles` (schemaVersion=2). Remove legacy `style` to avoid validator rejection.
38
37
  styles: mergedStyle,
39
- } as T;
38
+ };
39
+ // Remove legacy `style` key if it was inherited from defaults or node attributes.
40
+ delete mergedRecord.style;
40
41
  if (
41
- mergedAttributes &&
42
- typeof (mergedAttributes as any).style === 'object' &&
43
- (mergedAttributes as any).style != null &&
44
- Object.keys((mergedAttributes as any).style).length === 0
42
+ typeof mergedRecord.styles === 'object' &&
43
+ mergedRecord.styles != null &&
44
+ Object.keys(mergedRecord.styles as Record<string, unknown>).length === 0
45
45
  ) {
46
- delete (mergedAttributes as any).style;
47
- delete (mergedAttributes as any).styles;
46
+ delete mergedRecord.styles;
48
47
  }
49
- return { ...node, attributes: mergedAttributes };
48
+ return { ...node, attributes: mergedRecord as T };
50
49
  }
@@ -1,12 +1,12 @@
1
1
  import { AttributesEditor } from '../AttributesEditor';
2
- import type { Node } from '../types/Node';
2
+ import type { Node, NodeData } from '../types/Node';
3
3
  import type { ProjectColors } from '../types/Project';
4
4
  import { useLogRender } from '../utils/useLogRender';
5
5
  import { useRenderStore } from '../store';
6
6
  import { findNodeByKey } from '../utils/nodeTree';
7
7
 
8
8
  interface AttributesEditorPanelProps {
9
- attributes: any;
9
+ attributes: Node;
10
10
  onChange: (data: Node) => void;
11
11
  projectColors?: ProjectColors;
12
12
  }
@@ -25,7 +25,7 @@ export function AttributesEditorPanel({
25
25
 
26
26
  const currentKey =
27
27
  typeof current === 'object' && !Array.isArray(current) && 'key' in current
28
- ? ((current as any).key as string | undefined)
28
+ ? (current as NodeData).key
29
29
  : undefined;
30
30
  const resolvedCurrent =
31
31
  currentKey && attributes
@@ -46,14 +46,13 @@ export function AttributesEditorPanel({
46
46
  });
47
47
  return changed ? arr : root;
48
48
  }
49
- const data = root as any;
49
+ const data = root as NodeData;
50
50
  if ('children' in data) {
51
51
  const prev = data.children;
52
52
  const replaced = Array.isArray(prev)
53
53
  ? prev.map((c: Node) => replaceNode(c, target, next))
54
54
  : replaceNode(prev as Node, target, next);
55
55
  if (replaced !== prev) {
56
- data.children = replaced;
57
56
  return { ...data, children: replaced } as Node;
58
57
  }
59
58
  }
@@ -452,14 +452,13 @@ export function Builder({
452
452
  });
453
453
  return changed ? arr : root;
454
454
  }
455
- const data = root as any;
455
+ const data = root as NodeData;
456
456
  if ('children' in data) {
457
457
  const prev = data.children;
458
458
  const replaced = Array.isArray(prev)
459
459
  ? prev.map((c: Node) => replaceNode(c, target, next))
460
460
  : replaceNode(prev as Node, target, next);
461
461
  if (replaced !== prev) {
462
- data.children = replaced;
463
462
  return { ...data, children: replaced } as Node;
464
463
  }
465
464
  }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useMemo } from 'react';
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
2
  import type { Product } from '../paywall/types/paywall-types';
3
3
  import type { PaywallBenefits } from '../paywall/types/benefits';
4
4
  import type { AppConfig } from '../types/PreviewConfig';
@@ -43,7 +43,7 @@ type BuilderProviderProps = {
43
43
  children: React.ReactNode;
44
44
  };
45
45
 
46
- const BuilderContext = createContext<BuilderProviderParams | undefined>(
46
+ const builderContext = createContext<BuilderProviderParams | undefined>(
47
47
  undefined,
48
48
  );
49
49
 
@@ -66,7 +66,7 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
66
66
  projectColors:
67
67
  params?.projectColors && typeof params.projectColors === 'object'
68
68
  ? (params.projectColors as ProjectColors)
69
- : undefined,
69
+ : defaultProjectColors,
70
70
  fonts: Array.isArray(params?.fonts) ? (params.fonts as Fonts) : undefined,
71
71
  appFont: params?.appFont,
72
72
  platform: params?.platform === 'native' ? 'native' : 'web',
@@ -91,16 +91,53 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
91
91
  );
92
92
 
93
93
  return (
94
- <BuilderContext.Provider value={value}>{children}</BuilderContext.Provider>
94
+ <builderContext.Provider value={value}>{children}</builderContext.Provider>
95
95
  );
96
96
  }
97
97
 
98
- export function useBuilderParams(): BuilderProviderParams {
98
+ const defaultProjectColors: Readonly<ProjectColors> = {
99
+ STATIC_COLORS: {
100
+ BLACK: '#000',
101
+ WHITE: '#FFFFFF',
102
+ TRANSPARENT: '#ffffff00',
103
+ ONBOARD_DOT_INACTIVE: '#E4E5E7',
104
+ ONBOARD_DOT_ACTIVE: '#007AFF',
105
+ BUTTON_PRIMARY_BACKGROUND: '#0066FF',
106
+ BUTTON_PRIMARY_TEXT: '#FFFFFF',
107
+ LINK_COLOR: '#1778F2',
108
+ SEPARATOR_COLOR: '#44454D',
109
+ },
110
+ THEME_COLORS: {
111
+ light: {
112
+ TEXT: '#161827',
113
+ BACKGROUND: '#F4F5FF',
114
+ ICON: '#0450E2',
115
+ LINE: '#E9EBF9',
116
+ ONBOARD_TITLE: '#161827',
117
+ ONBOARD_SUBTITLE: '#44454D',
118
+ BUTTON_SECONDARY_TEXT: '#81838F',
119
+ FOOTER_TEXT: '#81838F',
120
+ },
121
+ dark: {
122
+ TEXT: '#E9EBF9',
123
+ BACKGROUND: '#080A17',
124
+ ICON: '#0450E2',
125
+ LINE: '#161827',
126
+ ONBOARD_TITLE: '#FDFDFD',
127
+ ONBOARD_SUBTITLE: '#C7C7C7',
128
+ BUTTON_SECONDARY_TEXT: '#A9AAAC',
129
+ FOOTER_TEXT: '#A2A4B1',
130
+ },
131
+ },
132
+ } as const;
133
+
134
+ export function useBuilderParams(): Readonly<BuilderProviderParams> {
99
135
  return (
100
- useContext(BuilderContext) ?? {
136
+ useContext(builderContext) ?? {
101
137
  products: [],
102
138
  benefits: {},
103
139
  platform: 'web',
140
+ projectColors: defaultProjectColors,
104
141
  }
105
142
  );
106
143
  }
@@ -88,8 +88,8 @@ export function JsonTextEditor({
88
88
  const nextValue =
89
89
  isRecord(parsed) && 'data' in parsed
90
90
  ? {
91
- ...(parsed as any),
92
- data: wrapNodeInMain((parsed as any).data as Node),
91
+ ...parsed,
92
+ data: wrapNodeInMain(parsed.data as Node),
93
93
  }
94
94
  : wrapNodeInMain(parsed as Node);
95
95
 
@@ -4,7 +4,7 @@ import loadingAnimation from '../assets/loading_animation.json';
4
4
  export function LoadingComponent() {
5
5
  return (
6
6
  <div className="rb-loading">
7
- <Lottie animationData={loadingAnimation as any} loop autoplay />
7
+ <Lottie animationData={loadingAnimation} loop autoplay />
8
8
  </div>
9
9
  );
10
10
  }
@@ -43,9 +43,7 @@ export class RenderErrorBoundary extends React.Component<
43
43
  }
44
44
 
45
45
  componentDidCatch(error: Error, info: React.ErrorInfo) {
46
- const componentStack =
47
- (info as unknown as { componentStack?: string })?.componentStack ??
48
- undefined;
46
+ const componentStack = info.componentStack ?? undefined;
49
47
 
50
48
  this.setState({
51
49
  error,
package/src/index.ts CHANGED
@@ -91,3 +91,6 @@ export {
91
91
  } from './assets/samples/getSamples';
92
92
  export { getDefaultProject } from './utils/getDefaultProject';
93
93
  export type { EventObjectGenerated } from './build-components/OnboardButton/OnboardButtonProps.generated';
94
+
95
+ export { parseColor } from './utils/parseColor';
96
+ export type { ParseColorOptions } from './utils/parseColor';
@@ -77,7 +77,7 @@ function getStyleSubSchemaKeys(
77
77
  schema: Record<string, unknown> | undefined,
78
78
  ): Set<string> {
79
79
  if (!schema) return new Set();
80
- const style = (schema as any).style;
80
+ const style = schema.style;
81
81
  if (!isPlainObject(style)) return new Set();
82
82
  return new Set(Object.keys(style));
83
83
  }
@@ -195,7 +195,7 @@ export const migration_1_1_2_extract_component_attributes_from_style: Migration
195
195
  run: (project: Project): Project => {
196
196
  const styleKeys = buildStyleKeySet();
197
197
  const normalized = normalizeStyleAttributes(
198
- project.data as unknown as Node,
198
+ project.data as Node,
199
199
  styleKeys,
200
200
  );
201
201
 
@@ -205,7 +205,7 @@ export const migration_1_1_2_extract_component_attributes_from_style: Migration
205
205
  return {
206
206
  ...project,
207
207
  version: '1.1.2',
208
- data: migrated as any,
208
+ data: migrated as Project['data'],
209
209
  };
210
210
  },
211
211
  };
@@ -34,7 +34,7 @@ function normalizeBenefits(raw: unknown): PaywallBenefits {
34
34
  }
35
35
 
36
36
  function getPresetMap(): PresetMap {
37
- const raw = presetsJson as unknown as PresetMap;
37
+ const raw = presetsJson as PresetMap;
38
38
  const safe: PresetMap = {};
39
39
  Object.entries(raw ?? {}).forEach(([key, map]) => {
40
40
  const normalizedKey = typeof key === 'string' ? key.trim() : '';
@@ -25,7 +25,7 @@ function normalizeProduct(p: Product): Product {
25
25
  }
26
26
 
27
27
  function getPresetMap(): PresetMap {
28
- const raw = presetsJson as unknown as PresetMap;
28
+ const raw = presetsJson as PresetMap;
29
29
  const safe: PresetMap = {};
30
30
  Object.entries(raw ?? {}).forEach(([key, list]) => {
31
31
  if (!key || typeof key !== 'string') return;
@@ -54,7 +54,8 @@ export function DebugJsonPage({
54
54
  typeof setAppConfig === 'function' && typeof appConfig?.theme === 'string';
55
55
  const canToggleRtl =
56
56
  typeof setAppConfig === 'function' &&
57
- typeof (appConfig as any)?.isRtl !== 'undefined';
57
+ appConfig != null &&
58
+ 'isRtl' in appConfig;
58
59
 
59
60
  const isRecord = (v: unknown): v is Record<string, unknown> =>
60
61
  typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -64,7 +65,7 @@ export function DebugJsonPage({
64
65
  // - raw Node JSON
65
66
  // - Project wrapper JSON { name, version, data: Node }
66
67
  if (isRecord(value) && 'data' in value) {
67
- return (value as any).data as Node;
68
+ return value.data as Node;
68
69
  }
69
70
  return value as Node;
70
71
  };
@@ -222,7 +223,9 @@ export function DebugJsonPage({
222
223
  {canToggleRtl ? (
223
224
  <Checkbox
224
225
  label="Is RTL"
225
- checked={Boolean((appConfig as any)?.isRtl)}
226
+ checked={Boolean(
227
+ (appConfig as AppConfig & { isRtl?: boolean })?.isRtl,
228
+ )}
226
229
  onChange={(checked) =>
227
230
  setAppConfig!({ ...(appConfig as AppConfig), isRtl: checked })
228
231
  }
@@ -245,7 +248,7 @@ export function DebugJsonPage({
245
248
  <div className="localication-modal__editor">
246
249
  <JsonTextEditor
247
250
  rootName="node"
248
- value={data ?? ({} as any)}
251
+ value={data ?? ({} as Record<string, unknown>)}
249
252
  onChange={(next) => {
250
253
  const nodeCandidate = extractNode(next);
251
254
  const processed = analyseAndProccess(
@@ -300,7 +300,7 @@ export function ProjectDebug({
300
300
  className="rb-project-debug__preview"
301
301
  aria-label="Preview"
302
302
  style={{
303
- ['--rb-canvas-bg' as any]: canvasBg ?? 'none',
303
+ ['--rb-canvas-bg' as string]: canvasBg ?? 'none',
304
304
  }}
305
305
  >
306
306
  <RenderErrorBoundary
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
- import type { Node } from '../types/Node';
2
+ import type { Node, NodeData } from '../types/Node';
3
3
  import type { Project, ProjectColors } from '../types/Project';
4
4
  import { ToastContainer, toast } from 'react-toastify';
5
5
  import { RenderPage } from '../RenderPage';
@@ -248,8 +248,8 @@ export function ProjectPage({
248
248
  if (bypassValidation) {
249
249
  // Best-effort: let the user continue with the raw data even if invalid.
250
250
  // This may still crash the preview, but it unblocks users for debugging.
251
- setEditorData(activeProject.data as unknown as Node);
252
- setCurrent(activeProject.data as unknown as Node);
251
+ setEditorData(activeProject.data as Node);
252
+ setCurrent(activeProject.data as Node);
253
253
  return;
254
254
  }
255
255
  if (isEmptyProjectData) {
@@ -346,8 +346,8 @@ export function ProjectPage({
346
346
  onContinueWithoutValidation={() => {
347
347
  setBypassValidation(true);
348
348
  setMigrationGate(null);
349
- setEditorData(activeProject.data as unknown as Node);
350
- setCurrent(activeProject.data as unknown as Node);
349
+ setEditorData(activeProject.data as Node);
350
+ setCurrent(activeProject.data as Node);
351
351
  setMinLoadingDelayDone(true);
352
352
  }}
353
353
  onMigrateNow={() => {
@@ -366,12 +366,12 @@ export function ProjectPage({
366
366
  isNodeLike(activeProject) &&
367
367
  !(
368
368
  isRecord(activeProject) &&
369
- typeof (activeProject as any).version === 'string'
369
+ typeof activeProject.version === 'string'
370
370
  )
371
371
  ? getDefaultProject({
372
372
  name: `imported-${Math.random().toString(36).slice(2, 8)}`,
373
373
  version: CURRENT_PROJECT_VERSION,
374
- data: activeProject as unknown as Node,
374
+ data: activeProject as Node,
375
375
  })
376
376
  : activeProject;
377
377
 
@@ -381,8 +381,8 @@ export function ProjectPage({
381
381
  setOverrideProject(migratedProject);
382
382
  setBypassValidation(true);
383
383
  setMigrationGate(null);
384
- setEditorData(migratedProject.data as unknown as Node);
385
- setCurrent(migratedProject.data as unknown as Node);
384
+ setEditorData(migratedProject.data as Node);
385
+ setCurrent(migratedProject.data as Node);
386
386
  setMinLoadingDelayDone(true);
387
387
  } finally {
388
388
  setIsMigrating(false);
@@ -400,20 +400,18 @@ export function ProjectPage({
400
400
  ): v is 'paywall' | 'onboard' | 'other' =>
401
401
  v === 'paywall' || v === 'onboard' || v === 'other';
402
402
 
403
+ const activeRecord = isRecord(activeProject) ? activeProject : null;
403
404
  const fixedName =
404
- typeof (activeProject as any)?.name === 'string' &&
405
- String((activeProject as any).name).trim()
406
- ? String((activeProject as any).name).trim()
405
+ typeof activeRecord?.name === 'string' &&
406
+ String(activeRecord.name).trim()
407
+ ? String(activeRecord.name).trim()
407
408
  : `imported-${Math.random().toString(36).slice(2, 8)}`;
408
409
 
409
- const activeAny = (
410
- isRecord(activeProject) ? activeProject : null
411
- ) as Record<string, unknown> | null;
412
410
  const nodeCandidate = (
413
- activeAny && 'data' in activeAny
414
- ? (activeAny as any).data
411
+ activeRecord && 'data' in activeRecord
412
+ ? activeRecord.data
415
413
  : isNodeLike(activeProject)
416
- ? (activeProject as unknown as Node)
414
+ ? (activeProject as Node)
417
415
  : null
418
416
  ) as Node | null;
419
417
 
@@ -421,10 +419,12 @@ export function ProjectPage({
421
419
  name: fixedName,
422
420
  version: CURRENT_PROJECT_VERSION,
423
421
  data: nodeCandidate,
424
- appConfig: (activeAny as any)?.appConfig,
425
- projectColors: (activeAny as any)?.projectColors,
426
- type: isAllowedProjectType((activeAny as any)?.type)
427
- ? ((activeAny as any).type as any)
422
+ appConfig: activeRecord?.appConfig,
423
+ projectColors: activeRecord?.projectColors as
424
+ | ProjectColors
425
+ | undefined,
426
+ type: isAllowedProjectType(activeRecord?.type)
427
+ ? (activeRecord.type as 'paywall' | 'onboard' | 'other')
428
428
  : undefined,
429
429
  });
430
430
 
@@ -452,8 +452,8 @@ export function ProjectPage({
452
452
  setBypassValidation(true);
453
453
  setValidationError(null);
454
454
  setValidationErrorStack(null);
455
- setEditorData(activeProject.data as unknown as Node);
456
- setCurrent(activeProject.data as unknown as Node);
455
+ setEditorData(activeProject.data as Node);
456
+ setCurrent(activeProject.data as Node);
457
457
  setMinLoadingDelayDone(true);
458
458
  }}
459
459
  onSaveEditedRawData={(nextRawData) => {
@@ -470,10 +470,9 @@ export function ProjectPage({
470
470
  let nextVersion: string | undefined;
471
471
 
472
472
  if (isRecord(parsed) && 'data' in parsed) {
473
- nodeCandidate = (parsed as Record<string, unknown>).data;
474
- const maybeName = (parsed as Record<string, unknown>).name;
475
- const maybeVersion = (parsed as Record<string, unknown>)
476
- .version;
473
+ nodeCandidate = parsed.data;
474
+ const maybeName = parsed.name;
475
+ const maybeVersion = parsed.version;
477
476
  if (typeof maybeName === 'string') nextName = maybeName;
478
477
  if (typeof maybeVersion === 'string')
479
478
  nextVersion = maybeVersion;
@@ -602,7 +601,7 @@ export function ProjectPage({
602
601
  style={{
603
602
  // Set as a CSS variable so `.dark .split-right` can override it.
604
603
 
605
- ['--rb-canvas-bg' as any]: `url(${getImage(
604
+ ['--rb-canvas-bg' as string]: `url(${getImage(
606
605
  TribeAssetName.Background,
607
606
  )})`,
608
607
  }}
@@ -632,7 +631,7 @@ export function ProjectPage({
632
631
  previewMode,
633
632
  selectedKey:
634
633
  current && typeof current === 'object' && 'key' in current
635
- ? ((current as any).key as string | undefined)
634
+ ? ((current as NodeData).key as string | undefined)
636
635
  : undefined,
637
636
  }}
638
637
  />
@@ -669,9 +668,9 @@ export function ProjectPage({
669
668
  data &&
670
669
  typeof data === 'object' &&
671
670
  !Array.isArray(data) &&
672
- 'key' in (data as any)
671
+ 'key' in data
673
672
  ) {
674
- nodeKey = (data as any).key as string | undefined;
673
+ nodeKey = (data as NodeData).key;
675
674
  }
676
675
  logger.verbose(
677
676
  'ProjectPage',
@@ -43,12 +43,12 @@ export function ProjectValidationPage({
43
43
  typeof v === 'object' && v !== null && !Array.isArray(v);
44
44
 
45
45
  const nodeCandidate: unknown =
46
- isRecord(parsed) && 'data' in parsed ? (parsed as any).data : parsed;
46
+ isRecord(parsed) && 'data' in parsed ? parsed.data : parsed;
47
47
 
48
48
  const wrapped = wrapNodeInMain(nodeCandidate as Node);
49
49
  const nextValue =
50
50
  isRecord(parsed) && 'data' in parsed
51
- ? { ...(parsed as any), data: wrapped }
51
+ ? { ...parsed, data: wrapped }
52
52
  : wrapped;
53
53
 
54
54
  setJsonText(JSON.stringify(nextValue, null, 2));
package/src/store.ts CHANGED
@@ -78,6 +78,12 @@ type RenderStore = {
78
78
  markFontLoaded: (fontFamily: string) => void;
79
79
  listMaxNested: number;
80
80
  setListMaxNested: (depth: number) => void;
81
+
82
+ // OS bar color overrides (set by StatusBarColor / NavigationBarColor build components)
83
+ statusBarOverrideColor: string | null;
84
+ setStatusBarOverrideColor: (color: string | null) => void;
85
+ navBarOverrideColor: string | null;
86
+ setNavBarOverrideColor: (color: string | null) => void;
81
87
  };
82
88
 
83
89
  export const useRenderStore = createWithEqualityFn<RenderStore>()(
@@ -240,6 +246,13 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
240
246
  set({
241
247
  listMaxNested: Number.isFinite(depth) && depth > 0 ? depth : 5,
242
248
  }),
249
+
250
+ // OS bar color overrides
251
+ statusBarOverrideColor: null,
252
+ setStatusBarOverrideColor: (color) =>
253
+ set({ statusBarOverrideColor: color }),
254
+ navBarOverrideColor: null,
255
+ setNavBarOverrideColor: (color) => set({ navBarOverrideColor: color }),
243
256
  }),
244
257
  {
245
258
  name: 'render-store',
@@ -175,6 +175,12 @@
175
175
  > :not(:first-child) {
176
176
  margin-left: sizes.$spaceComfy;
177
177
  }
178
+ // When the parent button has sort controls (↑↓), the button text is pushed
179
+ // right by the sort-controls width (32px). Increase child indent so children
180
+ // stay visually nested under the parent label, not under the sort icons.
181
+ > .builder__button:first-child:has(.builder__sort-controls) ~ * {
182
+ margin-left: 32px + sizes.$spaceComfy;
183
+ }
178
184
  &::before {
179
185
  content: '';
180
186
  position: absolute;
@@ -19,7 +19,7 @@ function looksLikeSelectOptionObject(
19
19
  if (keys.length < 1 || keys.length > 3) return false;
20
20
  const allowed = new Set(['value', 'label', 'id']);
21
21
  for (const k of keys) if (!allowed.has(k)) return false;
22
- const v = (value as Record<string, unknown>).value;
22
+ const v = value.value;
23
23
  return (
24
24
  v == null ||
25
25
  typeof v === 'string' ||
@@ -61,10 +61,10 @@ export function normalizeNodeForValidation(
61
61
  }
62
62
 
63
63
  if (isNodeArray(node)) {
64
- const nodeArray = node as unknown as Node<NodeDefaultAttribute>[];
64
+ const nodeArray = node as Node<NodeDefaultAttribute>[];
65
65
  return nodeArray.map(
66
66
  normalizeNodeForValidation,
67
- ) as unknown as Node<NodeDefaultAttribute>;
67
+ ) as Node<NodeDefaultAttribute>;
68
68
  }
69
69
 
70
70
  const recordData = node as NodeData<NodeDefaultAttribute>;
@@ -76,7 +76,7 @@ export function normalizeNodeForValidation(
76
76
  attributes = {};
77
77
  }
78
78
  attributes = isPlainObject(attributes)
79
- ? (normalizeUnknownValue(attributes) as Record<string, unknown>)
79
+ ? normalizeUnknownValue(attributes)
80
80
  : attributes;
81
81
 
82
82
  const children =
@@ -88,7 +88,7 @@ export function normalizeNodeForValidation(
88
88
 
89
89
  return {
90
90
  ...recordData,
91
- attributes: attributes as any,
91
+ attributes: attributes as NodeDefaultAttribute,
92
92
  children,
93
93
  };
94
94
  }
@@ -21,10 +21,10 @@ function assignMissingKeys(
21
21
  }
22
22
 
23
23
  if (isNodeArray(node)) {
24
- const nodeArray = node as unknown as Node<NodeDefaultAttribute>[];
24
+ const nodeArray = node as Node<NodeDefaultAttribute>[];
25
25
  return nodeArray.map((child) =>
26
26
  assignMissingKeys(child, usedKeys),
27
- ) as unknown as Node<NodeDefaultAttribute>;
27
+ ) as Node<NodeDefaultAttribute>;
28
28
  }
29
29
 
30
30
  const recordData = node as NodeData<NodeDefaultAttribute>;