@developer_tribe/react-builder 1.2.29 → 1.2.30

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 (79) hide show
  1. package/dist/RenderPage.d.ts +7 -2
  2. package/dist/attributes-editor/attributesEditorModelTypes.d.ts +0 -1
  3. package/dist/build-components/index.generated.d.ts +38 -0
  4. package/dist/components/BuilderProvider.d.ts +9 -15
  5. package/dist/hooks/useLocalize.d.ts +3 -2
  6. package/dist/hooks/usePreviewSelection.d.ts +12 -0
  7. package/dist/index.cjs.js +1 -28
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.esm.js +1 -28
  11. package/dist/index.esm.js.map +1 -1
  12. package/dist/index.web.cjs.js +4 -4
  13. package/dist/index.web.cjs.js.map +1 -1
  14. package/dist/index.web.esm.js +4 -4
  15. package/dist/index.web.esm.js.map +1 -1
  16. package/dist/logger.d.ts +3 -6
  17. package/dist/modals/IconPickerModal.d.ts +1 -1
  18. package/dist/pages/DebugJsonPage.d.ts +1 -4
  19. package/dist/size-matters/index.d.ts +15 -6
  20. package/dist/store.d.ts +5 -3
  21. package/dist/types/Icons.generated.d.ts +2 -0
  22. package/dist/types/PreviewConfig.d.ts +6 -8
  23. package/dist/types/Project.d.ts +4 -3
  24. package/dist/utils/extractTextStyle/extractTextStyle.d.ts +2 -0
  25. package/dist/utils/extractTextStyle/extractTextStyleNative.d.ts +2 -0
  26. package/dist/utils/extractViewStyle/extractViewStyle.d.ts +2 -0
  27. package/dist/utils/extractViewStyle/extractViewStyleNative.d.ts +2 -0
  28. package/package.json +1 -1
  29. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +19 -9
  30. package/src/RenderPage.tsx +66 -57
  31. package/src/assets/.DS_Store +0 -0
  32. package/src/assets/meta.json +1 -1
  33. package/src/assets/samples/carousel-sample.json +2 -6
  34. package/src/assets/samples/getSamples.ts +14 -4
  35. package/src/attribute-analyser/style/native/useExtractImageStyle.ts +3 -3
  36. package/src/attribute-analyser/style/native/useExtractTextStyle.ts +8 -2
  37. package/src/attribute-analyser/style/native/useExtractViewStyle.ts +7 -3
  38. package/src/attribute-analyser/style/web/useExtractImageStyle.ts +3 -3
  39. package/src/attribute-analyser/style/web/useExtractTextStyle.ts +8 -2
  40. package/src/attribute-analyser/style/web/useExtractViewStyle.ts +3 -3
  41. package/src/attributes-editor/AttributesEditorFields.tsx +1 -1
  42. package/src/attributes-editor/attributesEditorModelTypes.ts +0 -3
  43. package/src/attributes-editor/useAttributesEditorModel.ts +0 -3
  44. package/src/build-components/BIcon/BIcon.tsx +1 -1
  45. package/src/build-components/Button/Button.tsx +2 -2
  46. package/src/build-components/CarouselDots/CarouselDots.tsx +3 -3
  47. package/src/build-components/OnboardButton/OnboardButton.tsx +2 -2
  48. package/src/build-components/OnboardDot/OnboardDot.tsx +9 -3
  49. package/src/build-components/OnboardFooter/OnboardFooter.tsx +4 -5
  50. package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +1 -1
  51. package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButton.tsx +2 -2
  52. package/src/build-components/Text/Text.tsx +2 -2
  53. package/src/build-components/index.generated.ts +184 -0
  54. package/src/components/BottomBar.tsx +7 -9
  55. package/src/components/BuilderProvider.tsx +47 -84
  56. package/src/components/EditorHeader.tsx +6 -3
  57. package/src/hooks/useLocalize.ts +14 -10
  58. package/src/hooks/usePreviewSelection.ts +66 -0
  59. package/src/index.ts +0 -2
  60. package/src/logger.ts +4 -20
  61. package/src/modals/IconPickerModal.tsx +1 -1
  62. package/src/modals/InspectModal.tsx +6 -7
  63. package/src/pages/DebugJsonPage.tsx +0 -6
  64. package/src/pages/ProjectPage.tsx +12 -57
  65. package/src/pages/tabs/SideTool.tsx +7 -7
  66. package/src/product-base/extractAndroidParams.ts +4 -11
  67. package/src/product-base/extractIOSParams.ts +4 -10
  68. package/src/size-matters/index.ts +44 -31
  69. package/src/store.ts +12 -6
  70. package/src/styles/modals/_inspect-modal.scss +7 -3
  71. package/src/types/Icons.generated.ts +244 -0
  72. package/src/types/PreviewConfig.ts +5 -9
  73. package/src/types/Project.ts +4 -3
  74. package/src/utils/extractImageStyle.ts +4 -2
  75. package/src/utils/extractTextStyle/extractTextStyle.ts +6 -1
  76. package/src/utils/extractTextStyle/extractTextStyleNative.ts +4 -1
  77. package/src/utils/extractViewStyle/extractViewStyle.ts +7 -5
  78. package/src/utils/extractViewStyle/extractViewStyleNative.ts +3 -1
  79. package/src/utils/getDefaultProject.ts +0 -1
@@ -1,27 +1,31 @@
1
1
  import { useCallback } from 'react';
2
- import type { AppConfig } from '../types/PreviewConfig';
3
- import { defaultAppConfig } from '../types/PreviewConfig';
2
+ import { type Localication, defaultLocalization } from '../types/PreviewConfig';
4
3
  import { useBuilderParams } from '../components/BuilderProvider';
5
4
  import { useLocalizationParams } from './useLocalizationParams';
6
5
  import { replaceLocalizationParams } from '../utils/replaceLocalizationParams';
7
6
 
8
7
  export type LocalizeFn = (keyOrText: string) => string;
9
8
 
10
- export function useLocalize(options?: { appConfig?: AppConfig }): LocalizeFn {
9
+ export function useLocalize(options?: {
10
+ localization?: Localication;
11
+ defaultLanguage?: string;
12
+ }): LocalizeFn {
11
13
  const {
12
- appConfig: builderAppConfig,
13
- defaultLanguage: builderDefaultLanguage,
14
+ localization: builderLocalization,
15
+ mockDefaultLanguage: builderDefaultLanguage,
14
16
  } = useBuilderParams();
15
- const appConfig = options?.appConfig ?? builderAppConfig ?? defaultAppConfig;
16
- const defaultLanguage = builderDefaultLanguage ?? 'en';
17
- const { localication } = appConfig;
17
+
18
+ const localization =
19
+ options?.localization ?? builderLocalization ?? defaultLocalization;
20
+ const defaultLanguage =
21
+ options?.defaultLanguage ?? builderDefaultLanguage ?? 'en';
18
22
  const params = useLocalizationParams();
19
23
 
20
24
  return useCallback(
21
25
  (keyOrText: string) => {
22
- const raw = localication?.[defaultLanguage]?.[keyOrText] ?? keyOrText;
26
+ const raw = localization?.[defaultLanguage]?.[keyOrText] ?? keyOrText;
23
27
  return replaceLocalizationParams(raw, params);
24
28
  },
25
- [defaultLanguage, localication, params],
29
+ [defaultLanguage, localization, params],
26
30
  );
27
31
  }
@@ -0,0 +1,66 @@
1
+ import { useEffect, type RefObject } from 'react';
2
+ import type { Node } from '../types/Node';
3
+ import { findNodeByKeyNested } from '../utils/findNodeByKeyNested';
4
+
5
+ type UsePreviewSelectionParams = {
6
+ previewMode: boolean;
7
+ data: Node;
8
+ rootRef: RefObject<HTMLDivElement>;
9
+ onSelectNode?: (node: Node | null) => void;
10
+ setCurrent: (node: Node) => void;
11
+ forceRender: number;
12
+ };
13
+
14
+ export function usePreviewSelection({
15
+ previewMode,
16
+ data,
17
+ rootRef,
18
+ onSelectNode,
19
+ setCurrent,
20
+ forceRender,
21
+ }: UsePreviewSelectionParams) {
22
+ useEffect(() => {
23
+ if (!previewMode) {
24
+ return;
25
+ }
26
+
27
+ const root = rootRef.current;
28
+ if (!root) {
29
+ return;
30
+ }
31
+
32
+ const handleClick = (event: MouseEvent) => {
33
+ const target = event.target as HTMLElement | null;
34
+
35
+ if (!target) return;
36
+
37
+ // Ignore clicks on carousel dots to avoid interfering with navigation
38
+ if (target.closest('.embla__dot')) {
39
+ return;
40
+ }
41
+
42
+ // Some build-components may attach synthetic keys (e.g. React `useId()`),
43
+ // which are not present in the persisted node tree. Walk up until we find
44
+ // an attribute-key that resolves to a real node.
45
+ let element = target.closest('[attribute-key]') as HTMLElement | null;
46
+ while (element) {
47
+ const key = element.getAttribute('attribute-key');
48
+ if (key) {
49
+ const node = findNodeByKeyNested(data, key);
50
+ if (node) {
51
+ setCurrent(node);
52
+ onSelectNode?.(node);
53
+ return;
54
+ }
55
+ }
56
+ element = element.parentElement?.closest('[attribute-key]') ?? null;
57
+ }
58
+ };
59
+
60
+ root.addEventListener('click', handleClick);
61
+
62
+ return () => {
63
+ root.removeEventListener('click', handleClick);
64
+ };
65
+ }, [previewMode, data, onSelectNode, setCurrent, forceRender, rootRef]); // forceRender: retrigger effect when we want to force a refresh (e.g. route change)
66
+ }
package/src/index.ts CHANGED
@@ -16,13 +16,11 @@ export type { Node, NodeData, NodeDefaultAttribute } from './types/Node';
16
16
  export type { Project, ProjectColors, ProjectMeta } from './types/Project';
17
17
  export type { Device } from './types/Device';
18
18
  export type {
19
- AppConfig,
20
19
  Theme,
21
20
  Localication,
22
21
  LocalizationKey,
23
22
  } from './types/PreviewConfig';
24
23
  export {
25
- defaultAppConfig,
26
24
  defaultTheme,
27
25
  defaultLocalization,
28
26
  mergeLocalization,
package/src/logger.ts CHANGED
@@ -6,33 +6,17 @@
6
6
  */
7
7
 
8
8
  type LogPayload = Record<string, unknown>;
9
- type LogOptions = { remote?: boolean };
10
9
 
11
10
  function noop() {}
12
-
11
+ //TODO: bizim logger'ı kullan
13
12
  export const iapLogger = {
14
- error(
15
- _tags: string[],
16
- message: string,
17
- payload?: LogPayload,
18
- _opts?: LogOptions,
19
- ) {
13
+ error(_tags: string[], message: string, payload?: LogPayload) {
20
14
  console.error(`[iap] ${message}`, payload);
21
15
  },
22
- warn(
23
- _tags: string[],
24
- message: string,
25
- payload?: LogPayload,
26
- _opts?: LogOptions,
27
- ) {
16
+ warn(_tags: string[], message: string, payload?: LogPayload) {
28
17
  console.warn(`[iap] ${message}`, payload);
29
18
  },
30
- info(
31
- _tags: string[],
32
- message: string,
33
- payload?: LogPayload,
34
- _opts?: LogOptions,
35
- ) {
19
+ info(_tags: string[], message: string, payload?: LogPayload) {
36
20
  console.info(`[iap] ${message}`, payload);
37
21
  },
38
22
  debug: noop,
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo, useState } from 'react';
2
2
  import Modal from './Modal';
3
- import { Icons, type IconsType } from '../types/Icons';
3
+ import { Icons, type IconsType } from '../types/Icons.generated';
4
4
  import { Icon } from '../components/Icon.generated';
5
5
 
6
6
  type IconPickerModalProps = {
@@ -15,14 +15,13 @@ type InspectModalProps = {
15
15
  export function InspectModal({ onClose }: InspectModalProps) {
16
16
  const [activeTab, setActiveTab] = useState<InspectTab>('localizations');
17
17
 
18
- const { appConfig, projectColors, theme, defaultLanguage } = useRenderStore(
19
- (s) => ({
20
- appConfig: s.appConfig,
18
+ const { localization, projectColors, theme, defaultLanguage } =
19
+ useRenderStore((s) => ({
20
+ localization: s.localization,
21
21
  projectColors: s.projectColors,
22
22
  theme: s.theme,
23
23
  defaultLanguage: s.defaultLanguage,
24
- }),
25
- );
24
+ }));
26
25
 
27
26
  const paramsSnapshot = useMemo(() => getLastParamsSnapshot(), []);
28
27
 
@@ -69,7 +68,7 @@ export function InspectModal({ onClose }: InspectModalProps) {
69
68
  <div className="inspect-modal__body">
70
69
  {activeTab === 'localizations' && (
71
70
  <LocalizationsTab
72
- localication={appConfig.localication ?? {}}
71
+ localication={localization ?? {}}
73
72
  language={defaultLanguage}
74
73
  />
75
74
  )}
@@ -107,7 +106,7 @@ function LocalizationsTab({
107
106
  if (entries.length === 0) {
108
107
  return (
109
108
  <p className="inspect-modal__empty">
110
- No localization keys found for "{language}".
109
+ No localization keys found for &quot;{language}&quot;.
111
110
  </p>
112
111
  );
113
112
  }
@@ -1,7 +1,6 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
2
  import type { Node } from '../types/Node';
3
3
  import type { NodeData, NodeDefaultAttribute } from '../types/Node';
4
- import type { AppConfig } from '../types/PreviewConfig';
5
4
  import { Checkbox } from '../components/Checkbox';
6
5
  import { JsonTextEditor } from '../components/JsonTextEditor';
7
6
  import { analyseAndProccess } from '../utils/analyseNode';
@@ -24,9 +23,6 @@ export type DebugJsonPageProps = {
24
23
  previewMode?: boolean;
25
24
  setPreviewMode?: (next: boolean) => void;
26
25
 
27
- appConfig?: AppConfig;
28
- setAppConfig?: (next: AppConfig) => void;
29
-
30
26
  logLabel?: string;
31
27
  };
32
28
 
@@ -39,8 +35,6 @@ export function DebugJsonPage({
39
35
  description,
40
36
  previewMode,
41
37
  setPreviewMode,
42
- appConfig,
43
- setAppConfig,
44
38
  logLabel,
45
39
  }: DebugJsonPageProps) {
46
40
  const setCurrent = useRenderStore((s) => s.setCurrent);
@@ -7,13 +7,7 @@ import { EditorHeader } from '../components/EditorHeader';
7
7
  import { AttributesEditorPanel } from '../components/AttributesEditorPanel';
8
8
  import { BuilderPanel } from './tabs/BuilderPanel';
9
9
  import { BottomBar } from '../components/BottomBar';
10
- import {
11
- type AppConfig,
12
- defaultAppConfig,
13
- defaultLocalization,
14
- mergeLocalization,
15
- type Localication,
16
- } from '../types/PreviewConfig';
10
+ import { type Localication } from '../types/PreviewConfig';
17
11
  import { useRenderStore } from '../store';
18
12
  import { logger } from '../utils/logger';
19
13
  import { useLogRender } from '../utils/useLogRender';
@@ -24,7 +18,6 @@ import {
24
18
  isNodeNullOrUndefined,
25
19
  } from '../utils/analyseNode';
26
20
  import { getImage, TribeAssetName } from '../utils/getImage';
27
- import type { PaywallBenefits } from '../paywall/types/benefits';
28
21
  import { LoadingComponent } from '../components/LoadingComponent';
29
22
  import { ProjectValidationPage } from './ProjectValidationPage';
30
23
  import { ProjectMigrationPage } from './ProjectMigrationPage';
@@ -89,32 +82,18 @@ export function ProjectPage({
89
82
  setCurrent,
90
83
  setProjectColors,
91
84
  setProjectName,
92
- setAppConfig,
93
- storeAppConfig,
94
- storeTheme,
95
- storeDefaultLanguage,
85
+ setLocalization,
96
86
  products,
97
87
  benefits,
98
- previewMode,
99
88
  } = useRenderStore((s) => ({
100
89
  current: s.current,
101
90
  setCurrent: s.setCurrent,
102
91
  setProjectColors: s.setProjectColors,
103
92
  setProjectName: s.setProjectName,
104
- setAppConfig: s.setAppConfig,
105
- storeAppConfig: s.appConfig,
106
- storeTheme: s.theme,
107
- storeDefaultLanguage: s.defaultLanguage,
93
+ setLocalization: s.setLocalization,
108
94
  products: s.products,
109
95
  benefits: s.benefits,
110
- previewMode: s.previewMode,
111
96
  }));
112
- const resolvedAppConfig: AppConfig = localization
113
- ? {
114
- ...defaultAppConfig,
115
- localication: mergeLocalization(defaultLocalization, localization),
116
- }
117
- : (storeAppConfig ?? defaultAppConfig);
118
97
  const [overrideProject, setOverrideProject] = useState<Project | null>(null);
119
98
  const activeProject = overrideProject ?? project;
120
99
  const resolvedName = name ?? activeProject.name;
@@ -184,15 +163,16 @@ export function ProjectPage({
184
163
  useEffect(() => {
185
164
  logger.info('ProjectPage', 'mount', { projectName: project.name });
186
165
  setOverrideProject(null);
187
- if (localization) {
188
- const merged = mergeLocalization(defaultLocalization, localization);
189
- setAppConfig({ ...defaultAppConfig, localication: merged });
190
- logger.verbose('ProjectPage', 'localization applied', merged);
191
- }
192
166
  return () => {
193
167
  logger.info('ProjectPage', 'unmount');
194
168
  };
195
- }, [localization, project.name, setAppConfig]);
169
+ }, [project.name]);
170
+
171
+ useEffect(() => {
172
+ if (localization) {
173
+ setLocalization(localization);
174
+ }
175
+ }, [localization, setLocalization]);
196
176
 
197
177
  useEffect(() => {
198
178
  setProjectName(resolvedName);
@@ -460,10 +440,7 @@ export function ProjectPage({
460
440
  name: fixedName,
461
441
  version: CURRENT_PROJECT_VERSION,
462
442
  data: nodeCandidate,
463
- appConfig: activeRecord?.appConfig,
464
- projectColors: activeRecord?.projectColors as
465
- | ProjectColors
466
- | undefined,
443
+ projectColors: {},
467
444
  type: isAllowedProjectType(activeRecord?.type)
468
445
  ? (activeRecord.type as 'paywall' | 'onboard' | 'other')
469
446
  : undefined,
@@ -655,29 +632,7 @@ export function ProjectPage({
655
632
  )}
656
633
  {/* NOTE: In React Native, `products` should come from an IAP wrapper (e.g. `react-native-iap`). */}
657
634
  {!showLoading && (
658
- <RenderPage
659
- data={editorData}
660
- name={resolvedName}
661
- params={{
662
- mockProducts: products,
663
- mockBenefits:
664
- benefits && typeof benefits === 'object'
665
- ? (benefits as PaywallBenefits)
666
- : {},
667
- // Colors/fonts/theme must be passed via BuilderProvider params so build-components never touch useRenderStore.
668
- theme: storeTheme,
669
- defaultLanguage: storeDefaultLanguage,
670
- appConfig: resolvedAppConfig,
671
- projectColors: resolvedProjectColors,
672
- fonts: typography.fonts,
673
- appFont: resolvedAppFont,
674
- previewMode,
675
- selectedKey:
676
- current && typeof current === 'object' && 'key' in current
677
- ? ((current as NodeData).key as string | undefined)
678
- : undefined,
679
- }}
680
- />
635
+ <RenderPage data={editorData} name={resolvedName} />
681
636
  )}
682
637
  </div>
683
638
  {/* BOTOM BAR */}
@@ -19,8 +19,8 @@ export function SideTool({ data, setData }: SideToolProps) {
19
19
  const [isLocalicationModalOpen, setIsLocalicationModalOpen] = useState(false);
20
20
  const [isCompactPanelVisible, setIsCompactPanelVisible] = useState(false);
21
21
  const {
22
- appConfig,
23
- setAppConfig,
22
+ localization,
23
+ setLocalization,
24
24
  theme,
25
25
  setTheme,
26
26
  defaultLanguage,
@@ -30,8 +30,8 @@ export function SideTool({ data, setData }: SideToolProps) {
30
30
  isRtl,
31
31
  setIsRtl,
32
32
  } = useRenderStore((s) => ({
33
- appConfig: s.appConfig,
34
- setAppConfig: s.setAppConfig,
33
+ localization: s.localization,
34
+ setLocalization: s.setLocalization,
35
35
  theme: s.theme,
36
36
  setTheme: s.setTheme,
37
37
  defaultLanguage: s.defaultLanguage,
@@ -43,7 +43,7 @@ export function SideTool({ data, setData }: SideToolProps) {
43
43
  }));
44
44
 
45
45
  const handleLocalicationChange = (data: Localication) => {
46
- setAppConfig({ ...appConfig, localication: data });
46
+ setLocalization(data);
47
47
  };
48
48
 
49
49
  return (
@@ -63,7 +63,7 @@ export function SideTool({ data, setData }: SideToolProps) {
63
63
  value={defaultLanguage}
64
64
  onChange={(e) => setDefaultLanguage(e.target.value)}
65
65
  >
66
- {Object.keys(appConfig.localication ?? {}).map((language) => (
66
+ {Object.keys(localization ?? {}).map((language) => (
67
67
  <option key={language} value={language}>
68
68
  {language}
69
69
  </option>
@@ -129,7 +129,7 @@ export function SideTool({ data, setData }: SideToolProps) {
129
129
 
130
130
  {isLocalicationModalOpen && (
131
131
  <LocalicationModal
132
- data={appConfig.localication ?? {}}
132
+ data={localization ?? {}}
133
133
  onChange={handleLocalicationChange}
134
134
  onClose={() => setIsLocalicationModalOpen(false)}
135
135
  />
@@ -62,7 +62,6 @@ export function extractAndroidParams(
62
62
  requestedOfferId: offerId,
63
63
  availableOffers: subscriptionOffers.map((o) => o.id),
64
64
  },
65
- { remote: true },
66
65
  );
67
66
  }
68
67
  }
@@ -102,15 +101,10 @@ export function extractAndroidParams(
102
101
  pricingPhases[pricingPhases.length - 1];
103
102
 
104
103
  if (!regularPhase) {
105
- iapLogger.error(
106
- ['extractAndroidParams'],
107
- 'No regular phase found',
108
- {
109
- productId: product.id || product.productId,
110
- pricingPhasesCount: pricingPhases.length,
111
- },
112
- { remote: true },
113
- );
104
+ iapLogger.error(['extractAndroidParams'], 'No regular phase found', {
105
+ productId: product.id || product.productId,
106
+ pricingPhasesCount: pricingPhases.length,
107
+ });
114
108
  return getEmptyParams();
115
109
  }
116
110
 
@@ -178,7 +172,6 @@ export function extractAndroidParams(
178
172
  productId: product?.id || product?.productId,
179
173
  error: error instanceof Error ? error.message : String(error),
180
174
  },
181
- { remote: true },
182
175
  );
183
176
  return getEmptyParams();
184
177
  }
@@ -134,7 +134,6 @@ export function extractIOSParams(
134
134
  (d: IOSDiscount) => d.identifier,
135
135
  ),
136
136
  },
137
- { remote: true },
138
137
  );
139
138
  }
140
139
  }
@@ -163,15 +162,10 @@ export function extractIOSParams(
163
162
  pricePerYear,
164
163
  };
165
164
  } catch (error) {
166
- iapLogger.error(
167
- ['extractIOSParams'],
168
- 'Failed to extract iOS params',
169
- {
170
- productId: product?.id || product?.productId,
171
- error: error instanceof Error ? error.message : String(error),
172
- },
173
- { remote: true },
174
- );
165
+ iapLogger.error(['extractIOSParams'], 'Failed to extract iOS params', {
166
+ productId: product?.id || product?.productId,
167
+ error: error instanceof Error ? error.message : String(error),
168
+ });
175
169
  return getEmptyParams();
176
170
  }
177
171
  }
@@ -1,23 +1,31 @@
1
- import { useRenderStore } from '../store';
2
- import { defaultAppConfig } from '../types/PreviewConfig';
1
+ import { defaultBaseSize, type BaseSize } from '../types/PreviewConfig';
3
2
  import { getDefaultDevice } from '../utils/getDevices';
3
+ import type { Device } from '../types/Device';
4
4
 
5
5
  const fallbackDevice = getDefaultDevice();
6
- const fallbackBaseSize = defaultAppConfig.baseSize;
6
+ const fallbackBaseSize = defaultBaseSize;
7
7
 
8
8
  function ensureNumber(value: number | undefined, fallback: number) {
9
9
  return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
10
10
  }
11
11
 
12
- function getBaseDimensions() {
13
- const currentState = useRenderStore.getState();
14
- const device = currentState.device ?? fallbackDevice;
15
- const baseSize = currentState.appConfig?.baseSize ?? fallbackBaseSize;
16
-
17
- const deviceWidth = ensureNumber(device?.width, fallbackDevice.width);
18
- const deviceHeight = ensureNumber(device?.height, fallbackDevice.height);
19
- const baseWidth = ensureNumber(baseSize?.width, fallbackBaseSize.width);
20
- const baseHeight = ensureNumber(baseSize?.height, fallbackBaseSize.height);
12
+ /**
13
+ * Calculates scaling factors based on baseSize and current device dimensions.
14
+ */
15
+ export function getScalers(
16
+ customBaseSize: BaseSize = fallbackBaseSize,
17
+ customDevice: Device = fallbackDevice,
18
+ ) {
19
+ const deviceWidth = ensureNumber(customDevice?.width, fallbackDevice.width);
20
+ const deviceHeight = ensureNumber(
21
+ customDevice?.height,
22
+ fallbackDevice.height,
23
+ );
24
+ const baseWidth = ensureNumber(customBaseSize?.width, fallbackBaseSize.width);
25
+ const baseHeight = ensureNumber(
26
+ customBaseSize?.height,
27
+ fallbackBaseSize.height,
28
+ );
21
29
 
22
30
  const [shortDimension, longDimension] =
23
31
  deviceWidth < deviceHeight
@@ -25,46 +33,51 @@ function getBaseDimensions() {
25
33
  : [deviceHeight, deviceWidth];
26
34
 
27
35
  return {
28
- baseSize: { width: baseWidth, height: baseHeight },
29
- shortDimension,
30
- longDimension,
36
+ scale: (size: number) => (shortDimension / baseWidth) * size,
37
+ verticalScale: (size: number) => (longDimension / baseHeight) * size,
38
+ moderateScale: (size: number, factor = 0.5) => {
39
+ const s = (shortDimension / baseWidth) * size;
40
+ return size + (s - size) * factor;
41
+ },
31
42
  };
32
43
  }
33
- export function scale(size: number) {
34
- const { baseSize, shortDimension } = getBaseDimensions();
35
- return (shortDimension / baseSize.width) * size;
36
- }
37
- export function verticalScale(size: number) {
38
- const { baseSize, longDimension } = getBaseDimensions();
39
- return (longDimension / baseSize.height) * size;
40
- }
41
44
 
42
- export const s = scale;
43
- export const vs = verticalScale;
44
- export const fs = verticalScale;
45
+ // Deprecated: legacy exports that rely on defaults - prefer usage via getScalers or passing params
46
+ // For backward compatibility (if any), these will use defaults, effectively disabling dynamic store updates
47
+ export const s = (size: number) => getScalers().scale(size);
48
+ export const vs = (size: number) => getScalers().verticalScale(size);
49
+ export const fs = vs;
50
+ export const ms = (size: number, factor?: number) =>
51
+ getScalers().moderateScale(size, factor);
45
52
 
46
- export function parseSize(value?: string | number) {
53
+ export function parseSize(
54
+ value: string | number | undefined,
55
+ baseSize?: BaseSize,
56
+ device?: Device,
57
+ ) {
47
58
  if (value === undefined) return undefined;
48
59
  if (typeof value === 'number') {
49
60
  return value;
50
61
  }
51
62
 
63
+ const scalers = getScalers(baseSize, device);
64
+
52
65
  const raw = String(value).trim();
53
66
  const lower = raw.toLowerCase();
54
67
 
55
68
  // Handle explicit scalers via suffixes
56
69
  if (lower.endsWith('@s')) {
57
70
  const n = parseFloat(lower.slice(0, -2));
58
- return Number.isFinite(n) ? s(n) : raw;
71
+ return Number.isFinite(n) ? scalers.scale(n) : raw;
59
72
  }
60
73
  if (lower.endsWith('@vs')) {
61
74
  const n = parseFloat(lower.slice(0, -3));
62
- return Number.isFinite(n) ? vs(n) : raw;
75
+ return Number.isFinite(n) ? scalers.verticalScale(n) : raw;
63
76
  }
64
77
  if (lower.endsWith('@f') || lower.endsWith('@fs')) {
65
78
  const cut = lower.endsWith('@f') ? -2 : -3;
66
79
  const n = parseFloat(lower.slice(0, cut));
67
- return Number.isFinite(n) ? fs(n) : raw;
80
+ return Number.isFinite(n) ? scalers.verticalScale(n) : raw;
68
81
  }
69
82
 
70
83
  // Preserve percentage values as-is
@@ -79,7 +92,7 @@ export function parseSize(value?: string | number) {
79
92
  return Number.isFinite(n) ? n : raw;
80
93
  }
81
94
 
82
- // Plain numeric strings fall back to provided scaler
95
+ // Plain numeric strings fall back to provided scaler (implicit number)
83
96
  const numeric = parseFloat(lower);
84
97
  if (Number.isFinite(numeric)) {
85
98
  return numeric;
package/src/store.ts CHANGED
@@ -2,9 +2,11 @@ import { createWithEqualityFn } from 'zustand/traditional';
2
2
  import { shallow } from 'zustand/shallow';
3
3
  import type { Device } from './types/Device';
4
4
  import {
5
- defaultAppConfig,
5
+ defaultBaseSize,
6
+ defaultLocalization,
6
7
  defaultTheme,
7
- type AppConfig,
8
+ type BaseSize,
9
+ type Localication,
8
10
  type Theme,
9
11
  } from './types/PreviewConfig';
10
12
  import { getDefaultDevice } from './utils/getDevices';
@@ -29,14 +31,16 @@ type RenderStore = {
29
31
  incForceRender: () => void;
30
32
  device: Device;
31
33
  setDevice: (device: Device) => void;
32
- appConfig: AppConfig;
33
- setAppConfig: (appConfig: AppConfig) => void;
34
+ baseSize: BaseSize;
35
+ setBaseSize: (baseSize: BaseSize) => void;
34
36
  projectColors?: ProjectColors;
35
37
  setProjectColors: (projectColors?: ProjectColors) => void;
36
38
  theme: Theme;
37
39
  setTheme: (theme: Theme) => void;
38
40
  defaultLanguage: string;
39
41
  setDefaultLanguage: (lang: string) => void;
42
+ localization: Localication;
43
+ setLocalization: (localization: Localication) => void;
40
44
  isRtl: boolean;
41
45
  setIsRtl: (isRtl: boolean) => void;
42
46
  previewMode: boolean;
@@ -106,14 +110,16 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
106
110
  set((state) => ({ forceRender: state.forceRender + 1 })),
107
111
  device: getDefaultDevice(),
108
112
  setDevice: (device) => set({ device }),
109
- appConfig: defaultAppConfig,
110
- setAppConfig: (appConfig) => set({ appConfig }),
113
+ baseSize: defaultBaseSize,
114
+ setBaseSize: (baseSize) => set({ baseSize }),
111
115
  projectColors: undefined,
112
116
  setProjectColors: (projectColors) => set({ projectColors }),
113
117
  theme: defaultTheme,
114
118
  setTheme: (theme) => set({ theme }),
115
119
  defaultLanguage: 'en',
116
120
  setDefaultLanguage: (lang) => set({ defaultLanguage: lang }),
121
+ localization: defaultLocalization,
122
+ setLocalization: (localization) => set({ localization }),
117
123
  isRtl: false,
118
124
  setIsRtl: (isRtl) => set({ isRtl }),
119
125
  previewMode: false,
@@ -31,7 +31,9 @@
31
31
  font-weight: 500;
32
32
  color: colors.$mutedTextColor;
33
33
  border-bottom: 2px solid transparent;
34
- transition: color 0.15s, border-color 0.15s;
34
+ transition:
35
+ color 0.15s,
36
+ border-color 0.15s;
35
37
 
36
38
  &:hover {
37
39
  color: colors.$textColor;
@@ -99,7 +101,8 @@
99
101
  }
100
102
 
101
103
  .inspect-modal__cell-key {
102
- font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', monospace;
104
+ font-family:
105
+ ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', monospace;
103
106
  font-size: sizes.$fontSizeXs;
104
107
  word-break: break-all;
105
108
  max-width: 320px;
@@ -110,7 +113,8 @@
110
113
  }
111
114
 
112
115
  .inspect-modal__cell-value {
113
- font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', monospace;
116
+ font-family:
117
+ ui-monospace, 'SF Mono', 'Cascadia Code', 'Segoe UI Mono', monospace;
114
118
  font-size: sizes.$fontSizeXs;
115
119
  word-break: break-all;
116
120
  display: flex;