@billdog.io/react-native 1.0.0-beta.2
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/BillDogReactNative.podspec +30 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/android/build.gradle +99 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
- package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
- package/dist/AdvancedPaywallCache.d.ts +70 -0
- package/dist/AdvancedPaywallCache.js +344 -0
- package/dist/BillDog.d.ts +255 -0
- package/dist/BillDog.js +918 -0
- package/dist/BillDogConfiguration.d.ts +145 -0
- package/dist/BillDogConfiguration.js +229 -0
- package/dist/ComponentRenderer.d.ts +17 -0
- package/dist/ComponentRenderer.js +886 -0
- package/dist/LayoutEngine.d.ts +101 -0
- package/dist/LayoutEngine.js +394 -0
- package/dist/LocalizationResolver.d.ts +99 -0
- package/dist/LocalizationResolver.js +266 -0
- package/dist/NativePaywallView.d.ts +36 -0
- package/dist/NativePaywallView.js +59 -0
- package/dist/PaywallCache.d.ts +74 -0
- package/dist/PaywallCache.js +293 -0
- package/dist/PaywallStateManager.d.ts +62 -0
- package/dist/PaywallStateManager.js +147 -0
- package/dist/PaywallView.d.ts +42 -0
- package/dist/PaywallView.js +275 -0
- package/dist/ProductVariableProvider.d.ts +60 -0
- package/dist/ProductVariableProvider.js +239 -0
- package/dist/VariableResolver.d.ts +120 -0
- package/dist/VariableResolver.js +379 -0
- package/dist/WebViewPaywall.d.ts +75 -0
- package/dist/WebViewPaywall.js +360 -0
- package/dist/animations/presets.d.ts +49 -0
- package/dist/animations/presets.js +367 -0
- package/dist/components/AccoladeCpsComponent.d.ts +13 -0
- package/dist/components/AccoladeCpsComponent.js +248 -0
- package/dist/components/AccordionComponent.d.ts +10 -0
- package/dist/components/AccordionComponent.js +187 -0
- package/dist/components/AnimatedWrapper.d.ts +35 -0
- package/dist/components/AnimatedWrapper.js +277 -0
- package/dist/components/CarouselComponent.d.ts +23 -0
- package/dist/components/CarouselComponent.js +484 -0
- package/dist/components/ChartCurveComponent.d.ts +13 -0
- package/dist/components/ChartCurveComponent.js +333 -0
- package/dist/components/CheckboxComponent.d.ts +9 -0
- package/dist/components/CheckboxComponent.js +172 -0
- package/dist/components/ConfettiComponent.d.ts +9 -0
- package/dist/components/ConfettiComponent.js +172 -0
- package/dist/components/CountdownComponent.d.ts +16 -0
- package/dist/components/CountdownComponent.js +414 -0
- package/dist/components/DelayedXComponent.d.ts +12 -0
- package/dist/components/DelayedXComponent.js +240 -0
- package/dist/components/DrawerModalComponent.d.ts +20 -0
- package/dist/components/DrawerModalComponent.js +286 -0
- package/dist/components/GradientView.d.ts +38 -0
- package/dist/components/GradientView.js +126 -0
- package/dist/components/HapticFeedbackComponent.d.ts +16 -0
- package/dist/components/HapticFeedbackComponent.js +64 -0
- package/dist/components/HorizontalProductsComponent.d.ts +17 -0
- package/dist/components/HorizontalProductsComponent.js +284 -0
- package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
- package/dist/components/MultiChoiceGridComponent.js +234 -0
- package/dist/components/NPSScaleComponent.d.ts +16 -0
- package/dist/components/NPSScaleComponent.js +260 -0
- package/dist/components/NavigationComponent.d.ts +26 -0
- package/dist/components/NavigationComponent.js +302 -0
- package/dist/components/OAuthButtonComponent.d.ts +20 -0
- package/dist/components/OAuthButtonComponent.js +237 -0
- package/dist/components/ProgressLoaderComponent.d.ts +13 -0
- package/dist/components/ProgressLoaderComponent.js +308 -0
- package/dist/components/ProgressStepperComponent.d.ts +18 -0
- package/dist/components/ProgressStepperComponent.js +228 -0
- package/dist/components/SegmentedPickerComponent.d.ts +10 -0
- package/dist/components/SegmentedPickerComponent.js +221 -0
- package/dist/components/ShareButtonsComponent.d.ts +21 -0
- package/dist/components/ShareButtonsComponent.js +265 -0
- package/dist/components/ShareCodeComponent.d.ts +16 -0
- package/dist/components/ShareCodeComponent.js +274 -0
- package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
- package/dist/components/SingleChoiceGridComponent.js +210 -0
- package/dist/components/SliderComponent.d.ts +9 -0
- package/dist/components/SliderComponent.js +263 -0
- package/dist/components/StarRatingInputComponent.d.ts +20 -0
- package/dist/components/StarRatingInputComponent.js +198 -0
- package/dist/components/StickerCompositeComponent.d.ts +13 -0
- package/dist/components/StickerCompositeComponent.js +136 -0
- package/dist/components/TabsComponent.d.ts +19 -0
- package/dist/components/TabsComponent.js +431 -0
- package/dist/components/TextInputComponent.d.ts +18 -0
- package/dist/components/TextInputComponent.js +227 -0
- package/dist/components/TimelineComponent.d.ts +19 -0
- package/dist/components/TimelineComponent.js +104 -0
- package/dist/components/ToggleComponent.d.ts +14 -0
- package/dist/components/ToggleComponent.js +132 -0
- package/dist/components/VerticalProductsComponent.d.ts +17 -0
- package/dist/components/VerticalProductsComponent.js +311 -0
- package/dist/dynamicValues.d.ts +36 -0
- package/dist/dynamicValues.js +142 -0
- package/dist/filters/dateFilters.d.ts +50 -0
- package/dist/filters/dateFilters.js +390 -0
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +253 -0
- package/dist/hooks/useOfferings.d.ts +76 -0
- package/dist/hooks/useOfferings.js +99 -0
- package/dist/hooks/usePaywall.d.ts +27 -0
- package/dist/hooks/usePaywall.js +114 -0
- package/dist/hooks/usePlacement.d.ts +34 -0
- package/dist/hooks/usePlacement.js +89 -0
- package/dist/hooks/useSubscription.d.ts +40 -0
- package/dist/hooks/useSubscription.js +97 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +198 -0
- package/dist/models/BillDogError.d.ts +40 -0
- package/dist/models/BillDogError.js +11 -0
- package/dist/models/CacheFetchPolicy.d.ts +8 -0
- package/dist/models/CacheFetchPolicy.js +4 -0
- package/dist/models/CustomProduct.d.ts +32 -0
- package/dist/models/CustomProduct.js +15 -0
- package/dist/models/CustomerInfo.d.ts +17 -0
- package/dist/models/CustomerInfo.js +11 -0
- package/dist/models/Delegate.d.ts +20 -0
- package/dist/models/Delegate.js +11 -0
- package/dist/models/EntitlementInfo.d.ts +19 -0
- package/dist/models/EntitlementInfo.js +2 -0
- package/dist/models/Placement.d.ts +54 -0
- package/dist/models/Placement.js +15 -0
- package/dist/models/PurchaseParams.d.ts +39 -0
- package/dist/models/PurchaseParams.js +60 -0
- package/dist/models/PurchaseResult.d.ts +30 -0
- package/dist/models/PurchaseResult.js +15 -0
- package/dist/models/RestoreResult.d.ts +30 -0
- package/dist/models/RestoreResult.js +22 -0
- package/dist/models/RestoredItem.d.ts +26 -0
- package/dist/models/RestoredItem.js +13 -0
- package/dist/modules/ABTest.d.ts +65 -0
- package/dist/modules/ABTest.js +113 -0
- package/dist/modules/Analytics.d.ts +91 -0
- package/dist/modules/Analytics.js +182 -0
- package/dist/modules/Survey.d.ts +65 -0
- package/dist/modules/Survey.js +82 -0
- package/dist/modules/VirtualCurrency.d.ts +63 -0
- package/dist/modules/VirtualCurrency.js +147 -0
- package/dist/murmur.d.ts +23 -0
- package/dist/murmur.js +104 -0
- package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
- package/dist/notifications/LiveActivitiesManager.js +106 -0
- package/dist/notifications/NotificationScheduler.d.ts +55 -0
- package/dist/notifications/NotificationScheduler.js +199 -0
- package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
- package/dist/notifications/PurchaseNotificationHandler.js +55 -0
- package/dist/notifications/useDeviceToken.d.ts +36 -0
- package/dist/notifications/useDeviceToken.js +97 -0
- package/dist/notifications/useLiveActivity.d.ts +39 -0
- package/dist/notifications/useLiveActivity.js +104 -0
- package/dist/review/ReviewAnalytics.d.ts +34 -0
- package/dist/review/ReviewAnalytics.js +73 -0
- package/dist/review/ReviewManager.d.ts +83 -0
- package/dist/review/ReviewManager.js +339 -0
- package/dist/review/ReviewTimingManager.d.ts +82 -0
- package/dist/review/ReviewTimingManager.js +224 -0
- package/dist/survey-core/types.d.ts +215 -0
- package/dist/survey-core/types.js +8 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.js +158 -0
- package/dist/utils/ApiClient.d.ts +65 -0
- package/dist/utils/ApiClient.js +132 -0
- package/dist/utils/CSSConverter.d.ts +49 -0
- package/dist/utils/CSSConverter.js +470 -0
- package/dist/utils/animations.d.ts +41 -0
- package/dist/utils/animations.js +194 -0
- package/dist/utils/calendarIntegration.d.ts +36 -0
- package/dist/utils/calendarIntegration.js +89 -0
- package/dist/utils/configurationResolver.d.ts +23 -0
- package/dist/utils/configurationResolver.js +78 -0
- package/dist/utils/deviceDetection.d.ts +38 -0
- package/dist/utils/deviceDetection.js +59 -0
- package/dist/utils/hapticFeedback.d.ts +17 -0
- package/dist/utils/hapticFeedback.js +53 -0
- package/dist/utils/iconMapper.d.ts +26 -0
- package/dist/utils/iconMapper.js +113 -0
- package/dist/utils/notificationPermissions.d.ts +13 -0
- package/dist/utils/notificationPermissions.js +51 -0
- package/dist/utils/priceFormatting.d.ts +19 -0
- package/dist/utils/priceFormatting.js +99 -0
- package/dist/utils/shareSheet.d.ts +17 -0
- package/dist/utils/shareSheet.js +42 -0
- package/dist/utils/subscriptionUpgrade.d.ts +84 -0
- package/dist/utils/subscriptionUpgrade.js +100 -0
- package/dist/valueUtils.d.ts +48 -0
- package/dist/valueUtils.js +129 -0
- package/dist/xFlowTags.d.ts +18 -0
- package/dist/xFlowTags.js +385 -0
- package/ios/BillDogABTest.m +50 -0
- package/ios/BillDogABTest.swift +188 -0
- package/ios/BillDogAnalytics.m +105 -0
- package/ios/BillDogAnalytics.swift +324 -0
- package/ios/BillDogBridge.m +222 -0
- package/ios/BillDogBridge.swift +715 -0
- package/ios/BillDogLiveActivities.m +41 -0
- package/ios/BillDogLiveActivities.swift +104 -0
- package/ios/BillDogPaywallViewManager.m +39 -0
- package/ios/BillDogPaywallViewManager.swift +361 -0
- package/ios/BillDogSurvey.m +34 -0
- package/ios/BillDogSurvey.swift +257 -0
- package/ios/BillDogVirtualCurrencyBridge.m +63 -0
- package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
- package/junit.xml +25 -0
- package/package.json +61 -0
- package/react-native.config.js +13 -0
- package/src/AdvancedPaywallCache.ts +433 -0
- package/src/BillDog.ts +983 -0
- package/src/BillDogConfiguration.ts +318 -0
- package/src/ComponentRenderer.tsx +1325 -0
- package/src/LayoutEngine.ts +459 -0
- package/src/LocalizationResolver.ts +311 -0
- package/src/NativePaywallView.tsx +132 -0
- package/src/PaywallCache.ts +358 -0
- package/src/PaywallStateManager.ts +154 -0
- package/src/PaywallView.tsx +328 -0
- package/src/ProductVariableProvider.ts +282 -0
- package/src/VariableResolver.ts +516 -0
- package/src/WebViewPaywall.tsx +454 -0
- package/src/animations/presets.ts +421 -0
- package/src/components/AccoladeCpsComponent.tsx +259 -0
- package/src/components/AccordionComponent.tsx +213 -0
- package/src/components/AnimatedWrapper.tsx +316 -0
- package/src/components/CarouselComponent.tsx +615 -0
- package/src/components/ChartCurveComponent.tsx +452 -0
- package/src/components/CheckboxComponent.tsx +185 -0
- package/src/components/ConfettiComponent.tsx +178 -0
- package/src/components/CountdownComponent.tsx +454 -0
- package/src/components/DelayedXComponent.tsx +266 -0
- package/src/components/DrawerModalComponent.tsx +324 -0
- package/src/components/GradientView.tsx +129 -0
- package/src/components/HapticFeedbackComponent.tsx +39 -0
- package/src/components/HorizontalProductsComponent.tsx +329 -0
- package/src/components/MultiChoiceGridComponent.tsx +265 -0
- package/src/components/NPSScaleComponent.tsx +294 -0
- package/src/components/NavigationComponent.tsx +329 -0
- package/src/components/OAuthButtonComponent.tsx +266 -0
- package/src/components/ProgressLoaderComponent.tsx +366 -0
- package/src/components/ProgressStepperComponent.tsx +250 -0
- package/src/components/SegmentedPickerComponent.tsx +245 -0
- package/src/components/ShareButtonsComponent.tsx +299 -0
- package/src/components/ShareCodeComponent.tsx +303 -0
- package/src/components/SingleChoiceGridComponent.tsx +229 -0
- package/src/components/SliderComponent.tsx +280 -0
- package/src/components/StarRatingInputComponent.tsx +223 -0
- package/src/components/StickerCompositeComponent.tsx +168 -0
- package/src/components/TabsComponent.tsx +496 -0
- package/src/components/TextInputComponent.tsx +258 -0
- package/src/components/TimelineComponent.tsx +129 -0
- package/src/components/ToggleComponent.tsx +142 -0
- package/src/components/VerticalProductsComponent.tsx +357 -0
- package/src/dynamicValues.ts +180 -0
- package/src/filters/dateFilters.ts +411 -0
- package/src/filters/index.ts +281 -0
- package/src/hooks/useOfferings.ts +192 -0
- package/src/hooks/usePaywall.ts +154 -0
- package/src/hooks/usePlacement.ts +143 -0
- package/src/hooks/useSubscription.ts +154 -0
- package/src/index.ts +245 -0
- package/src/models/BillDogError.ts +24 -0
- package/src/models/CacheFetchPolicy.ts +13 -0
- package/src/models/CustomProduct.ts +33 -0
- package/src/models/CustomerInfo.ts +26 -0
- package/src/models/Delegate.ts +22 -0
- package/src/models/EntitlementInfo.ts +27 -0
- package/src/models/Placement.ts +50 -0
- package/src/models/PurchaseParams.ts +81 -0
- package/src/models/PurchaseResult.ts +32 -0
- package/src/models/RestoreResult.ts +27 -0
- package/src/models/RestoredItem.ts +25 -0
- package/src/modules/ABTest.ts +131 -0
- package/src/modules/Analytics.ts +227 -0
- package/src/modules/Survey.ts +126 -0
- package/src/modules/VirtualCurrency.ts +223 -0
- package/src/murmur.ts +102 -0
- package/src/notifications/LiveActivitiesManager.ts +140 -0
- package/src/notifications/NotificationScheduler.ts +292 -0
- package/src/notifications/PurchaseNotificationHandler.ts +67 -0
- package/src/notifications/useDeviceToken.ts +116 -0
- package/src/notifications/useLiveActivity.ts +125 -0
- package/src/review/ReviewAnalytics.ts +89 -0
- package/src/review/ReviewManager.ts +355 -0
- package/src/review/ReviewTimingManager.ts +265 -0
- package/src/survey-core/types.ts +244 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/types.ts +680 -0
- package/src/utils/ApiClient.ts +163 -0
- package/src/utils/CSSConverter.ts +552 -0
- package/src/utils/animations.ts +232 -0
- package/src/utils/calendarIntegration.ts +113 -0
- package/src/utils/configurationResolver.ts +106 -0
- package/src/utils/deviceDetection.ts +83 -0
- package/src/utils/hapticFeedback.ts +55 -0
- package/src/utils/iconMapper.ts +102 -0
- package/src/utils/notificationPermissions.ts +48 -0
- package/src/utils/priceFormatting.ts +119 -0
- package/src/utils/shareSheet.ts +55 -0
- package/src/utils/subscriptionUpgrade.ts +183 -0
- package/src/valueUtils.ts +108 -0
- package/src/xFlowTags.ts +463 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified API Client for BillDog React Native SDK
|
|
3
|
+
*
|
|
4
|
+
* Centralizes all `fetch()` calls to BillDog edge functions with:
|
|
5
|
+
* - Automatic `api_key` injection into every JSON body
|
|
6
|
+
* - Endpoint resolution via `BillDogConfiguration.resolveEndpoint()`
|
|
7
|
+
* - Consistent `Content-Type: application/json` header
|
|
8
|
+
* - Error handling with structured exceptions
|
|
9
|
+
*
|
|
10
|
+
* This eliminates the previous fragmentation where 7 files independently
|
|
11
|
+
* constructed URLs, resolved auth, and called `fetch()` with inconsistent
|
|
12
|
+
* header/body patterns.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { BillDogConfiguration } from '../BillDogConfiguration';
|
|
16
|
+
|
|
17
|
+
export interface ApiClientResponse<T = any> {
|
|
18
|
+
data: T;
|
|
19
|
+
status: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ApiClientError extends Error {
|
|
23
|
+
public readonly status: number;
|
|
24
|
+
public readonly responseBody: any;
|
|
25
|
+
|
|
26
|
+
constructor(message: string, status: number, responseBody?: any) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'ApiClientError';
|
|
29
|
+
this.status = status;
|
|
30
|
+
this.responseBody = responseBody;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve endpoint URL — delegates to `BillDogConfiguration.resolveEndpoint()`
|
|
36
|
+
* (the single branded base, `https://api.billdog.io/v1`).
|
|
37
|
+
*/
|
|
38
|
+
function resolveEndpoint(functionName: string): string {
|
|
39
|
+
return BillDogConfiguration.resolveEndpoint(functionName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Worker-less fallback endpoint — `direct.billdog.io/functions/v1/<fn>`.
|
|
44
|
+
* `direct.billdog.io` is a Supabase custom domain with NO Cloudflare Worker,
|
|
45
|
+
* so it uses the native Supabase `/functions/v1/` path (not the Worker's
|
|
46
|
+
* `/v1/` alias). Used when `api.billdog.io` is unreachable.
|
|
47
|
+
*/
|
|
48
|
+
function directEndpoint(functionName: string): string {
|
|
49
|
+
return `https://direct.billdog.io/functions/v1/${functionName}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the API key.
|
|
54
|
+
* If a deprecated `apiKey` override is provided, it takes precedence.
|
|
55
|
+
* Otherwise reads from `BillDogConfiguration`.
|
|
56
|
+
*/
|
|
57
|
+
function resolveApiKey(apiKeyOverride?: string): string | undefined {
|
|
58
|
+
if (apiKeyOverride) return apiKeyOverride;
|
|
59
|
+
if (BillDogConfiguration.isConfigured()) {
|
|
60
|
+
return BillDogConfiguration.getApiKey();
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const ApiClient = {
|
|
66
|
+
/**
|
|
67
|
+
* POST to a BillDog edge function.
|
|
68
|
+
*
|
|
69
|
+
* - Injects `api_key` into the JSON body (standard auth contract).
|
|
70
|
+
* - Resolves endpoint via configuration.
|
|
71
|
+
* - Throws `ApiClientError` on non-2xx responses.
|
|
72
|
+
*
|
|
73
|
+
* @param functionName - Edge function name (e.g. 'fetch-paywall')
|
|
74
|
+
* @param body - Request body (api_key will be injected automatically)
|
|
75
|
+
* @param options - Optional overrides for backward compatibility
|
|
76
|
+
*/
|
|
77
|
+
async post<T = any>(
|
|
78
|
+
functionName: string,
|
|
79
|
+
body: Record<string, any> = {},
|
|
80
|
+
options?: {
|
|
81
|
+
/** @deprecated Use BillDogConfiguration.configure({ apiKey }) */
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
}
|
|
84
|
+
): Promise<T> {
|
|
85
|
+
const apiKey = resolveApiKey(options?.apiKey);
|
|
86
|
+
|
|
87
|
+
const fullBody = {
|
|
88
|
+
...body,
|
|
89
|
+
...(apiKey ? { api_key: apiKey } : {}),
|
|
90
|
+
};
|
|
91
|
+
const init: RequestInit = {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify(fullBody),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let response: Response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetch(resolveEndpoint(functionName), init);
|
|
102
|
+
// A gateway error means the api.billdog.io edge / Worker is unhealthy —
|
|
103
|
+
// retry once against the Worker-less direct path.
|
|
104
|
+
if (response.status === 502 || response.status === 503 || response.status === 504) {
|
|
105
|
+
try {
|
|
106
|
+
response = await fetch(directEndpoint(functionName), init);
|
|
107
|
+
} catch {
|
|
108
|
+
/* keep the gateway response — surfaced as the error below */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Transport failure — api.billdog.io is unreachable (Cloudflare Worker
|
|
113
|
+
// outage). Fall back to direct.billdog.io (plain DNS → Supabase, no Worker).
|
|
114
|
+
response = await fetch(directEndpoint(functionName), init);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const errorBody = await response.json().catch(() => ({ error: response.statusText }));
|
|
119
|
+
throw new ApiClientError(
|
|
120
|
+
errorBody.error || `Request to ${functionName} failed: ${response.statusText}`,
|
|
121
|
+
response.status,
|
|
122
|
+
errorBody
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return response.json();
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* GET request to a raw URL (e.g. version check endpoints).
|
|
131
|
+
* Does NOT inject api_key — use only for unauthenticated endpoints.
|
|
132
|
+
*/
|
|
133
|
+
async get<T = any>(url: string): Promise<T> {
|
|
134
|
+
const response = await fetch(url, {
|
|
135
|
+
method: 'GET',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
const errorBody = await response.json().catch(() => ({ error: response.statusText }));
|
|
143
|
+
throw new ApiClientError(
|
|
144
|
+
`GET ${url} failed: ${response.statusText}`,
|
|
145
|
+
response.status,
|
|
146
|
+
errorBody
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return response.json();
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convenience: resolve an API key for external use (e.g. hooks that need
|
|
155
|
+
* to check if a key is available before calling).
|
|
156
|
+
*/
|
|
157
|
+
resolveApiKey,
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Convenience: resolve an endpoint URL for external use.
|
|
161
|
+
*/
|
|
162
|
+
resolveEndpoint,
|
|
163
|
+
};
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS to React Native Style Converter
|
|
3
|
+
* Converts CSS strings to React Native StyleSheet-compatible objects
|
|
4
|
+
* Enhanced with gradient detection and unified style generation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ViewStyle, TextStyle, ImageStyle, DimensionValue } from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface GradientStop {
|
|
10
|
+
color: string;
|
|
11
|
+
position: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GradientConfig {
|
|
15
|
+
type: 'linear' | 'radial';
|
|
16
|
+
angle?: number;
|
|
17
|
+
stops: GradientStop[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CSSConversionResult {
|
|
21
|
+
style: ViewStyle | TextStyle | ImageStyle;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
/** If true, the component requires a LinearGradient wrapper */
|
|
24
|
+
requiresGradient?: boolean;
|
|
25
|
+
/** Gradient configuration for LinearGradient component */
|
|
26
|
+
gradient?: GradientConfig;
|
|
27
|
+
/** Fallback solid color if gradient can't be rendered */
|
|
28
|
+
fallbackColor?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Properties that only work in WebView, not native React Native
|
|
33
|
+
*/
|
|
34
|
+
const WEBVIEW_ONLY_PROPERTIES = [
|
|
35
|
+
'backdrop-filter',
|
|
36
|
+
'box-shadow', // Partially supported via shadow* props
|
|
37
|
+
'text-shadow',
|
|
38
|
+
'background-image',
|
|
39
|
+
'clip-path',
|
|
40
|
+
'mask',
|
|
41
|
+
'filter',
|
|
42
|
+
'mix-blend-mode',
|
|
43
|
+
'background-blend-mode',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse gradient from CSS background property
|
|
48
|
+
*/
|
|
49
|
+
function parseGradientFromCSS(value: string): GradientConfig | null {
|
|
50
|
+
// Match linear-gradient(angle, color1 pos1, color2 pos2, ...)
|
|
51
|
+
const linearMatch = value.match(/linear-gradient\(([^)]+)\)/);
|
|
52
|
+
if (linearMatch) {
|
|
53
|
+
const parts = linearMatch[1].split(',').map(p => p.trim());
|
|
54
|
+
let angle = 180;
|
|
55
|
+
let colorStops: string[] = [];
|
|
56
|
+
|
|
57
|
+
// First part might be angle
|
|
58
|
+
if (parts[0].includes('deg')) {
|
|
59
|
+
angle = parseFloat(parts[0].replace('deg', ''));
|
|
60
|
+
colorStops = parts.slice(1);
|
|
61
|
+
} else if (parts[0].startsWith('to ')) {
|
|
62
|
+
// Handle direction keywords
|
|
63
|
+
const dir = parts[0].replace('to ', '');
|
|
64
|
+
if (dir === 'right') angle = 90;
|
|
65
|
+
else if (dir === 'left') angle = 270;
|
|
66
|
+
else if (dir === 'bottom') angle = 180;
|
|
67
|
+
else if (dir === 'top') angle = 0;
|
|
68
|
+
colorStops = parts.slice(1);
|
|
69
|
+
} else {
|
|
70
|
+
colorStops = parts;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const stops: GradientStop[] = colorStops.map((stop, index) => {
|
|
74
|
+
const match = stop.match(/(.+?)\s+(\d+)%?$/);
|
|
75
|
+
if (match) {
|
|
76
|
+
return { color: match[1].trim(), position: parseFloat(match[2]) };
|
|
77
|
+
}
|
|
78
|
+
// Default position based on index
|
|
79
|
+
return {
|
|
80
|
+
color: stop.trim(),
|
|
81
|
+
position: (index / Math.max(colorStops.length - 1, 1)) * 100
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { type: 'linear', angle, stops };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Match radial-gradient
|
|
89
|
+
const radialMatch = value.match(/radial-gradient\(([^)]+)\)/);
|
|
90
|
+
if (radialMatch) {
|
|
91
|
+
const parts = radialMatch[1].split(',').map(p => p.trim());
|
|
92
|
+
// Skip shape/size specification, get color stops
|
|
93
|
+
const colorStops = parts.filter(p => !p.includes('circle') && !p.includes('ellipse'));
|
|
94
|
+
|
|
95
|
+
const stops: GradientStop[] = colorStops.map((stop, index) => {
|
|
96
|
+
const match = stop.match(/(.+?)\s+(\d+)%?$/);
|
|
97
|
+
if (match) {
|
|
98
|
+
return { color: match[1].trim(), position: parseFloat(match[2]) };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
color: stop.trim(),
|
|
102
|
+
position: (index / Math.max(colorStops.length - 1, 1)) * 100
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { type: 'radial', stops };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Convert CSS string to React Native styles
|
|
114
|
+
* Now with gradient detection and extraction
|
|
115
|
+
*/
|
|
116
|
+
export function convertCSSToReactNative(css: string): CSSConversionResult {
|
|
117
|
+
const style: ViewStyle = {};
|
|
118
|
+
const warnings: string[] = [];
|
|
119
|
+
let requiresGradient = false;
|
|
120
|
+
let gradient: GradientConfig | undefined;
|
|
121
|
+
let fallbackColor: string | undefined;
|
|
122
|
+
|
|
123
|
+
if (!css || css.trim() === '') {
|
|
124
|
+
return { style, warnings };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const properties = parseCSSProperties(css);
|
|
128
|
+
|
|
129
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
130
|
+
// Check for gradient in background property
|
|
131
|
+
if (property === 'background' || property === 'background-image') {
|
|
132
|
+
const gradientConfig = parseGradientFromCSS(value);
|
|
133
|
+
if (gradientConfig) {
|
|
134
|
+
requiresGradient = true;
|
|
135
|
+
gradient = gradientConfig;
|
|
136
|
+
fallbackColor = gradientConfig.stops[0]?.color || 'transparent';
|
|
137
|
+
style.backgroundColor = fallbackColor; // Fallback
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check for unsupported properties
|
|
143
|
+
if (WEBVIEW_ONLY_PROPERTIES.some(p => property.includes(p))) {
|
|
144
|
+
warnings.push(`'${property}' is not supported in native rendering`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
processProperty(property, value, style, warnings);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { style, warnings, requiresGradient, gradient, fallbackColor };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Parse CSS string into property map
|
|
156
|
+
*/
|
|
157
|
+
function parseCSSProperties(css: string): Record<string, string> {
|
|
158
|
+
const properties: Record<string, string> = {};
|
|
159
|
+
|
|
160
|
+
const declarations = css.split(';').filter(d => d.trim());
|
|
161
|
+
for (const decl of declarations) {
|
|
162
|
+
const colonIndex = decl.indexOf(':');
|
|
163
|
+
if (colonIndex > 0) {
|
|
164
|
+
const prop = decl.substring(0, colonIndex).trim();
|
|
165
|
+
const value = decl.substring(colonIndex + 1).trim();
|
|
166
|
+
properties[prop] = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return properties;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Process a single CSS property
|
|
175
|
+
*/
|
|
176
|
+
function processProperty(
|
|
177
|
+
property: string,
|
|
178
|
+
value: string,
|
|
179
|
+
style: ViewStyle,
|
|
180
|
+
warnings: string[]
|
|
181
|
+
): void {
|
|
182
|
+
const numericValue = parseNumericValue(value);
|
|
183
|
+
|
|
184
|
+
switch (property) {
|
|
185
|
+
// Background
|
|
186
|
+
case 'background-color':
|
|
187
|
+
style.backgroundColor = value;
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
// Opacity
|
|
191
|
+
case 'opacity':
|
|
192
|
+
style.opacity = parseFloat(value);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
// Border radius
|
|
196
|
+
case 'border-radius':
|
|
197
|
+
style.borderRadius = numericValue;
|
|
198
|
+
break;
|
|
199
|
+
case 'border-top-left-radius':
|
|
200
|
+
style.borderTopLeftRadius = numericValue;
|
|
201
|
+
break;
|
|
202
|
+
case 'border-top-right-radius':
|
|
203
|
+
style.borderTopRightRadius = numericValue;
|
|
204
|
+
break;
|
|
205
|
+
case 'border-bottom-left-radius':
|
|
206
|
+
style.borderBottomLeftRadius = numericValue;
|
|
207
|
+
break;
|
|
208
|
+
case 'border-bottom-right-radius':
|
|
209
|
+
style.borderBottomRightRadius = numericValue;
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
// Border
|
|
213
|
+
case 'border-width':
|
|
214
|
+
style.borderWidth = numericValue;
|
|
215
|
+
break;
|
|
216
|
+
case 'border-color':
|
|
217
|
+
style.borderColor = value;
|
|
218
|
+
break;
|
|
219
|
+
case 'border-style':
|
|
220
|
+
style.borderStyle = value as 'solid' | 'dotted' | 'dashed';
|
|
221
|
+
break;
|
|
222
|
+
case 'border-top-width':
|
|
223
|
+
style.borderTopWidth = numericValue;
|
|
224
|
+
break;
|
|
225
|
+
case 'border-right-width':
|
|
226
|
+
style.borderRightWidth = numericValue;
|
|
227
|
+
break;
|
|
228
|
+
case 'border-bottom-width':
|
|
229
|
+
style.borderBottomWidth = numericValue;
|
|
230
|
+
break;
|
|
231
|
+
case 'border-left-width':
|
|
232
|
+
style.borderLeftWidth = numericValue;
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
// Padding
|
|
236
|
+
case 'padding':
|
|
237
|
+
applySpacingShorthand(value, style, 'padding');
|
|
238
|
+
break;
|
|
239
|
+
case 'padding-top':
|
|
240
|
+
style.paddingTop = numericValue;
|
|
241
|
+
break;
|
|
242
|
+
case 'padding-right':
|
|
243
|
+
style.paddingRight = numericValue;
|
|
244
|
+
break;
|
|
245
|
+
case 'padding-bottom':
|
|
246
|
+
style.paddingBottom = numericValue;
|
|
247
|
+
break;
|
|
248
|
+
case 'padding-left':
|
|
249
|
+
style.paddingLeft = numericValue;
|
|
250
|
+
break;
|
|
251
|
+
|
|
252
|
+
// Margin
|
|
253
|
+
case 'margin':
|
|
254
|
+
applySpacingShorthand(value, style, 'margin');
|
|
255
|
+
break;
|
|
256
|
+
case 'margin-top':
|
|
257
|
+
style.marginTop = numericValue;
|
|
258
|
+
break;
|
|
259
|
+
case 'margin-right':
|
|
260
|
+
style.marginRight = numericValue;
|
|
261
|
+
break;
|
|
262
|
+
case 'margin-bottom':
|
|
263
|
+
style.marginBottom = numericValue;
|
|
264
|
+
break;
|
|
265
|
+
case 'margin-left':
|
|
266
|
+
style.marginLeft = numericValue;
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
// Dimensions
|
|
270
|
+
case 'width':
|
|
271
|
+
style.width = parseNumericOrPercent(value);
|
|
272
|
+
break;
|
|
273
|
+
case 'height':
|
|
274
|
+
style.height = parseNumericOrPercent(value);
|
|
275
|
+
break;
|
|
276
|
+
case 'min-width':
|
|
277
|
+
style.minWidth = parseNumericOrPercent(value);
|
|
278
|
+
break;
|
|
279
|
+
case 'min-height':
|
|
280
|
+
style.minHeight = parseNumericOrPercent(value);
|
|
281
|
+
break;
|
|
282
|
+
case 'max-width':
|
|
283
|
+
style.maxWidth = parseNumericOrPercent(value);
|
|
284
|
+
break;
|
|
285
|
+
case 'max-height':
|
|
286
|
+
style.maxHeight = parseNumericOrPercent(value);
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
// Transform
|
|
290
|
+
case 'transform':
|
|
291
|
+
// parseTransform returns a dynamically-built array of single-key transform
|
|
292
|
+
// objects from parsed CSS. RN's transform union (RotateTransform | ... |
|
|
293
|
+
// MatrixTransform) is impractical to satisfy from dynamic parsing, so cast.
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
295
|
+
style.transform = parseTransform(value) as any;
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
// Flexbox
|
|
299
|
+
case 'flex':
|
|
300
|
+
style.flex = parseFloat(value);
|
|
301
|
+
break;
|
|
302
|
+
case 'flex-direction':
|
|
303
|
+
style.flexDirection = value as 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
304
|
+
break;
|
|
305
|
+
case 'justify-content':
|
|
306
|
+
style.justifyContent = mapJustifyContent(value);
|
|
307
|
+
break;
|
|
308
|
+
case 'align-items':
|
|
309
|
+
style.alignItems = mapAlignItems(value);
|
|
310
|
+
break;
|
|
311
|
+
case 'align-self':
|
|
312
|
+
style.alignSelf = mapAlignItems(value);
|
|
313
|
+
break;
|
|
314
|
+
case 'flex-wrap':
|
|
315
|
+
style.flexWrap = value as 'wrap' | 'nowrap' | 'wrap-reverse';
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
// Shadow (partial support)
|
|
319
|
+
case 'box-shadow':
|
|
320
|
+
applyBoxShadow(value, style);
|
|
321
|
+
break;
|
|
322
|
+
|
|
323
|
+
// Position
|
|
324
|
+
case 'position':
|
|
325
|
+
style.position = value as 'absolute' | 'relative';
|
|
326
|
+
break;
|
|
327
|
+
case 'top':
|
|
328
|
+
style.top = numericValue;
|
|
329
|
+
break;
|
|
330
|
+
case 'right':
|
|
331
|
+
style.right = numericValue;
|
|
332
|
+
break;
|
|
333
|
+
case 'bottom':
|
|
334
|
+
style.bottom = numericValue;
|
|
335
|
+
break;
|
|
336
|
+
case 'left':
|
|
337
|
+
style.left = numericValue;
|
|
338
|
+
break;
|
|
339
|
+
case 'z-index':
|
|
340
|
+
style.zIndex = parseInt(value);
|
|
341
|
+
break;
|
|
342
|
+
|
|
343
|
+
// Overflow
|
|
344
|
+
case 'overflow':
|
|
345
|
+
style.overflow = value as 'visible' | 'hidden' | 'scroll';
|
|
346
|
+
break;
|
|
347
|
+
|
|
348
|
+
default:
|
|
349
|
+
// Unknown property - add warning
|
|
350
|
+
if (!property.startsWith('-webkit') && !property.startsWith('-moz')) {
|
|
351
|
+
warnings.push(`Unknown CSS property: '${property}'`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Parse numeric value from CSS
|
|
358
|
+
*/
|
|
359
|
+
function parseNumericValue(value: string): number {
|
|
360
|
+
return parseFloat(value.replace(/px|dp|pt|rem|em/g, '')) || 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Parse numeric or percentage value
|
|
365
|
+
*/
|
|
366
|
+
function parseNumericOrPercent(value: string): DimensionValue {
|
|
367
|
+
if (value.includes('%')) {
|
|
368
|
+
// RN's DimensionValue percent type is the template literal `${number}%`;
|
|
369
|
+
// a runtime-parsed CSS string can't be statically proven to match, so cast.
|
|
370
|
+
return value.trim() as DimensionValue;
|
|
371
|
+
}
|
|
372
|
+
return parseNumericValue(value);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Apply spacing shorthand (padding/margin)
|
|
377
|
+
*/
|
|
378
|
+
function applySpacingShorthand(
|
|
379
|
+
value: string,
|
|
380
|
+
style: ViewStyle,
|
|
381
|
+
prefix: 'padding' | 'margin'
|
|
382
|
+
): void {
|
|
383
|
+
const parts = value.split(/\s+/).map(parseNumericValue);
|
|
384
|
+
|
|
385
|
+
switch (parts.length) {
|
|
386
|
+
case 1:
|
|
387
|
+
(style as any)[prefix] = parts[0];
|
|
388
|
+
break;
|
|
389
|
+
case 2:
|
|
390
|
+
(style as any)[`${prefix}Vertical`] = parts[0];
|
|
391
|
+
(style as any)[`${prefix}Horizontal`] = parts[1];
|
|
392
|
+
break;
|
|
393
|
+
case 3:
|
|
394
|
+
(style as any)[`${prefix}Top`] = parts[0];
|
|
395
|
+
(style as any)[`${prefix}Horizontal`] = parts[1];
|
|
396
|
+
(style as any)[`${prefix}Bottom`] = parts[2];
|
|
397
|
+
break;
|
|
398
|
+
case 4:
|
|
399
|
+
(style as any)[`${prefix}Top`] = parts[0];
|
|
400
|
+
(style as any)[`${prefix}Right`] = parts[1];
|
|
401
|
+
(style as any)[`${prefix}Bottom`] = parts[2];
|
|
402
|
+
(style as any)[`${prefix}Left`] = parts[3];
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Parse CSS transform to React Native transform array
|
|
409
|
+
*/
|
|
410
|
+
function parseTransform(value: string): Array<{ [key: string]: number | string }> {
|
|
411
|
+
const transforms: Array<{ [key: string]: number | string }> = [];
|
|
412
|
+
|
|
413
|
+
// translateX
|
|
414
|
+
const translateXMatch = value.match(/translateX\(([^)]+)\)/);
|
|
415
|
+
if (translateXMatch) {
|
|
416
|
+
transforms.push({ translateX: parseNumericValue(translateXMatch[1]) });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// translateY
|
|
420
|
+
const translateYMatch = value.match(/translateY\(([^)]+)\)/);
|
|
421
|
+
if (translateYMatch) {
|
|
422
|
+
transforms.push({ translateY: parseNumericValue(translateYMatch[1]) });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// translate(x, y)
|
|
426
|
+
const translateMatch = value.match(/translate\(([^,)]+)(?:,\s*([^)]+))?\)/);
|
|
427
|
+
if (translateMatch) {
|
|
428
|
+
transforms.push({ translateX: parseNumericValue(translateMatch[1]) });
|
|
429
|
+
if (translateMatch[2]) {
|
|
430
|
+
transforms.push({ translateY: parseNumericValue(translateMatch[2]) });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// scale
|
|
435
|
+
const scaleMatch = value.match(/scale\(([^)]+)\)/);
|
|
436
|
+
if (scaleMatch) {
|
|
437
|
+
transforms.push({ scale: parseFloat(scaleMatch[1]) });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// scaleX
|
|
441
|
+
const scaleXMatch = value.match(/scaleX\(([^)]+)\)/);
|
|
442
|
+
if (scaleXMatch) {
|
|
443
|
+
transforms.push({ scaleX: parseFloat(scaleXMatch[1]) });
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// scaleY
|
|
447
|
+
const scaleYMatch = value.match(/scaleY\(([^)]+)\)/);
|
|
448
|
+
if (scaleYMatch) {
|
|
449
|
+
transforms.push({ scaleY: parseFloat(scaleYMatch[1]) });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// rotate
|
|
453
|
+
const rotateMatch = value.match(/rotate\(([^)]+)\)/);
|
|
454
|
+
if (rotateMatch) {
|
|
455
|
+
const deg = rotateMatch[1].replace('deg', '');
|
|
456
|
+
transforms.push({ rotate: `${deg}deg` });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return transforms;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Apply box-shadow (partial React Native support)
|
|
464
|
+
*/
|
|
465
|
+
function applyBoxShadow(value: string, style: ViewStyle): void {
|
|
466
|
+
// Parse: offsetX offsetY blurRadius color
|
|
467
|
+
const match = value.match(/(-?\d+)px\s+(-?\d+)px\s+(-?\d+)px(?:\s+(-?\d+)px)?\s+(.+)/);
|
|
468
|
+
if (match) {
|
|
469
|
+
const [, x, y, blur, _spread, color] = match;
|
|
470
|
+
|
|
471
|
+
style.shadowColor = color.trim();
|
|
472
|
+
style.shadowOffset = { width: parseInt(x), height: parseInt(y) };
|
|
473
|
+
style.shadowOpacity = 1;
|
|
474
|
+
style.shadowRadius = parseInt(blur);
|
|
475
|
+
// Android elevation
|
|
476
|
+
(style as any).elevation = Math.max(parseInt(blur), 1);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Map CSS justify-content to React Native
|
|
482
|
+
*/
|
|
483
|
+
function mapJustifyContent(value: string): ViewStyle['justifyContent'] {
|
|
484
|
+
const map: Record<string, ViewStyle['justifyContent']> = {
|
|
485
|
+
'flex-start': 'flex-start',
|
|
486
|
+
'flex-end': 'flex-end',
|
|
487
|
+
'center': 'center',
|
|
488
|
+
'space-between': 'space-between',
|
|
489
|
+
'space-around': 'space-around',
|
|
490
|
+
'space-evenly': 'space-evenly',
|
|
491
|
+
};
|
|
492
|
+
return map[value] || 'flex-start';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Map CSS align-items to React Native
|
|
497
|
+
*/
|
|
498
|
+
function mapAlignItems(value: string): ViewStyle['alignItems'] {
|
|
499
|
+
const map: Record<string, ViewStyle['alignItems']> = {
|
|
500
|
+
'flex-start': 'flex-start',
|
|
501
|
+
'flex-end': 'flex-end',
|
|
502
|
+
'center': 'center',
|
|
503
|
+
'stretch': 'stretch',
|
|
504
|
+
'baseline': 'baseline',
|
|
505
|
+
};
|
|
506
|
+
return map[value] || 'stretch';
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Validate CSS for native compatibility
|
|
511
|
+
*/
|
|
512
|
+
export function validateCSSForNative(css: string): string[] {
|
|
513
|
+
const warnings: string[] = [];
|
|
514
|
+
|
|
515
|
+
for (const prop of WEBVIEW_ONLY_PROPERTIES) {
|
|
516
|
+
if (css.includes(prop)) {
|
|
517
|
+
warnings.push(`'${prop}' is only supported in WebView mode`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Check for gradients (now supported with LinearGradient component)
|
|
522
|
+
if (css.includes('linear-gradient') || css.includes('radial-gradient')) {
|
|
523
|
+
warnings.push('Gradients require LinearGradient component wrapper');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return warnings;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Convert gradient angle to LinearGradient start/end points
|
|
531
|
+
* CSS angle: 0deg = bottom to top, 90deg = left to right
|
|
532
|
+
* LinearGradient: start/end are {x, y} where 0,0 is top-left
|
|
533
|
+
*/
|
|
534
|
+
export function angleToGradientPoints(angle: number): {
|
|
535
|
+
start: { x: number; y: number };
|
|
536
|
+
end: { x: number; y: number };
|
|
537
|
+
} {
|
|
538
|
+
const radians = ((angle - 90) * Math.PI) / 180;
|
|
539
|
+
|
|
540
|
+
const startX = 0.5 - Math.cos(radians) * 0.5;
|
|
541
|
+
const startY = 0.5 + Math.sin(radians) * 0.5;
|
|
542
|
+
const endX = 0.5 + Math.cos(radians) * 0.5;
|
|
543
|
+
const endY = 0.5 - Math.sin(radians) * 0.5;
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
start: { x: startX, y: startY },
|
|
547
|
+
end: { x: endX, y: endY },
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// GradientStop and GradientConfig are already exported at their `export interface`
|
|
552
|
+
// declarations above; the redundant re-export was removed to avoid a conflict.
|