@tfcorp/consumer-app-react-native-sdk 2.0.0-beta.1
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/LICENSE +70 -0
- package/README.md +210 -0
- package/TrueFitReactNativeWebViewSdk.podspec +28 -0
- package/android/build.gradle +52 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/truefit/reactnative/TrueFitRecommendationPackage.kt +13 -0
- package/android/src/main/java/com/truefit/reactnative/TrueFitRecommendationProps.kt +95 -0
- package/android/src/main/java/com/truefit/reactnative/TrueFitRecommendationView.kt +52 -0
- package/android/src/main/java/com/truefit/reactnative/TrueFitRecommendationViewManager.kt +91 -0
- package/ios/TrueFitRecommendationView.swift +186 -0
- package/ios/TrueFitRecommendationViewManager.m +12 -0
- package/ios/TrueFitRecommendationViewManager.swift +12 -0
- package/lib/commonjs/index.js +85 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/index.js +79 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/index.d.ts +60 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +50 -0
- package/react-native.config.js +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
True Fit SDK
|
|
2
|
+
Copyright © 2026 True Fit Corp. All rights reserved.
|
|
3
|
+
|
|
4
|
+
TRUE FIT SDK LICENSE AGREEMENT (EULA)
|
|
5
|
+
|
|
6
|
+
This is a legal agreement between you (either an individual or an entity, "You")
|
|
7
|
+
and True Fit Corp. ("True Fit") governing your use of the True Fit SDK, including
|
|
8
|
+
all libraries, sample code, tools, and accompanying documentation (collectively,
|
|
9
|
+
the "SDK"). By downloading, installing, or using the SDK, You agree to be bound
|
|
10
|
+
by the terms of this Agreement. If You do not agree, do not download, install, or
|
|
11
|
+
use the SDK.
|
|
12
|
+
|
|
13
|
+
1. PROPRIETARY SOFTWARE
|
|
14
|
+
The SDK is proprietary software of True Fit. It is licensed, not sold. True
|
|
15
|
+
Fit retains all right, title, and interest in and to the SDK, including all
|
|
16
|
+
intellectual property rights therein.
|
|
17
|
+
|
|
18
|
+
2. LICENSE GRANT
|
|
19
|
+
Subject to your compliance with this Agreement, True Fit grants You a
|
|
20
|
+
non-exclusive, non-transferable, revocable, worldwide license to:
|
|
21
|
+
- download and install the SDK;
|
|
22
|
+
- use the SDK to develop applications; and
|
|
23
|
+
- distribute applications incorporating the SDK to your customers.
|
|
24
|
+
|
|
25
|
+
3. RESTRICTIONS
|
|
26
|
+
You may not:
|
|
27
|
+
- redistribute the SDK itself;
|
|
28
|
+
- sell, sublicense, or transfer the SDK independently;
|
|
29
|
+
- modify, reverse engineer, decompile, or disassemble the SDK, except where
|
|
30
|
+
such restriction is prohibited by applicable law;
|
|
31
|
+
- use the SDK to create a competing SDK or service.
|
|
32
|
+
|
|
33
|
+
4. OWNERSHIP
|
|
34
|
+
Except for the limited license expressly granted in Section 2, no rights,
|
|
35
|
+
licenses, or interests in the SDK are granted to You. All rights not expressly
|
|
36
|
+
granted are reserved by True Fit.
|
|
37
|
+
|
|
38
|
+
5. TERM AND TERMINATION
|
|
39
|
+
This Agreement is effective until terminated. It terminates automatically,
|
|
40
|
+
without notice, if You breach any of its terms. Upon termination, You must
|
|
41
|
+
cease all use of the SDK and destroy all copies in your possession or control.
|
|
42
|
+
Sections 1, 3, 4, and 6 through 9 survive termination.
|
|
43
|
+
|
|
44
|
+
6. DISCLAIMER OF WARRANTY
|
|
45
|
+
THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE", WITHOUT WARRANTY OF ANY KIND,
|
|
46
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
|
|
47
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. TRUE
|
|
48
|
+
FIT DOES NOT WARRANT THAT THE SDK WILL BE UNINTERRUPTED OR ERROR-FREE.
|
|
49
|
+
|
|
50
|
+
7. LIMITATION OF LIABILITY
|
|
51
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL TRUE FIT
|
|
52
|
+
BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE
|
|
53
|
+
DAMAGES, OR ANY LOSS OF PROFITS, REVENUE, OR DATA, ARISING OUT OF OR RELATED
|
|
54
|
+
TO YOUR USE OF OR INABILITY TO USE THE SDK, WHETHER IN CONTRACT, TORT, OR
|
|
55
|
+
OTHERWISE, EVEN IF TRUE FIT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
56
|
+
DAMAGES.
|
|
57
|
+
|
|
58
|
+
8. GOVERNING LAW
|
|
59
|
+
This Agreement is governed by the laws of the Commonwealth of Massachusetts,
|
|
60
|
+
United States of America, without regard to its conflict-of-laws principles.
|
|
61
|
+
The state and federal courts located in Boston, Massachusetts have exclusive
|
|
62
|
+
jurisdiction over any dispute arising out of or relating to this Agreement.
|
|
63
|
+
|
|
64
|
+
9. ENTIRE AGREEMENT
|
|
65
|
+
This Agreement constitutes the entire agreement between You and True Fit
|
|
66
|
+
regarding the SDK and supersedes all prior or contemporaneous understandings.
|
|
67
|
+
If any provision is held unenforceable, the remaining provisions remain in
|
|
68
|
+
full force and effect.
|
|
69
|
+
|
|
70
|
+
For licensing inquiries, contact: dev@truefit.com
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# True Fit React Native WebView SDK
|
|
2
|
+
|
|
3
|
+
React Native bridge for the True Fit recommendation WebView SDK on Android and iOS.
|
|
4
|
+
|
|
5
|
+
The widget renders inline on a product page, sizes itself to its content, and
|
|
6
|
+
presents the size modal full screen on its own. It uses the official True Fit
|
|
7
|
+
`tfcapi` event contract for open/close, height, and recommendation events, and
|
|
8
|
+
materializes the account sign-in popup (`window.open`) so shoppers can register
|
|
9
|
+
and get a personalized recommendation.
|
|
10
|
+
|
|
11
|
+
This package exposes a single native view:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {
|
|
15
|
+
TrueFitRecommendationView,
|
|
16
|
+
type TrueFitPresentationMode,
|
|
17
|
+
type TrueFitWidgetResult,
|
|
18
|
+
} from 'consumer-app-react-native-sdk';
|
|
19
|
+
|
|
20
|
+
export function ProductFitRecommendation() {
|
|
21
|
+
return (
|
|
22
|
+
<TrueFitRecommendationView
|
|
23
|
+
config={{
|
|
24
|
+
storeKey: 'nda',
|
|
25
|
+
storeStyle: '7093242789933',
|
|
26
|
+
}}
|
|
27
|
+
options={{
|
|
28
|
+
minWidgetHeight: 86,
|
|
29
|
+
maxWidgetHeight: 320,
|
|
30
|
+
}}
|
|
31
|
+
onPresentationModeChange={(mode: TrueFitPresentationMode, styleId) => {
|
|
32
|
+
console.log('True Fit mode', mode, styleId);
|
|
33
|
+
}}
|
|
34
|
+
onHeightChange={(height) => {
|
|
35
|
+
console.log('True Fit height', height);
|
|
36
|
+
}}
|
|
37
|
+
onWidgetResult={(result: TrueFitWidgetResult) => {
|
|
38
|
+
console.log('True Fit widget result', result);
|
|
39
|
+
}}
|
|
40
|
+
onAutoSizeSelect={({ styleId, size }) => {
|
|
41
|
+
console.log('True Fit auto size select', styleId, size);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Props
|
|
49
|
+
|
|
50
|
+
### `config`
|
|
51
|
+
|
|
52
|
+
| Name | Type | Default |
|
|
53
|
+
| ------------- | ------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
54
|
+
| `environment` | `'development' \| 'prod'` | `'prod'` |
|
|
55
|
+
| `storeKey` | `string` | Required |
|
|
56
|
+
| `storeStyle` | `string` | Required |
|
|
57
|
+
| `uiLocale` | `string` | Not set |
|
|
58
|
+
| `scriptUrl` | `string` | — (overrides `environment`; a full built-bundle URL. Raw Vite `.ts` dev entries are **not** supported) |
|
|
59
|
+
|
|
60
|
+
### `options`
|
|
61
|
+
|
|
62
|
+
| Name | Type | Default |
|
|
63
|
+
| -------------------------------------- | --------- | ------- |
|
|
64
|
+
| `minWidgetHeight` | `number` | `86` |
|
|
65
|
+
| `maxWidgetHeight` | `number` | `320` |
|
|
66
|
+
| `modalBackdropOpacity` | `number` | `0.4` |
|
|
67
|
+
| `modalTransitionDurationMs` | `number` | `200` |
|
|
68
|
+
| `disableBackgroundScrollWhenModalOpen` | `boolean` | `true` |
|
|
69
|
+
| `heightAnimationDurationMs` | `number` | `180` |
|
|
70
|
+
| `debugLogging` | `boolean` | `false` |
|
|
71
|
+
|
|
72
|
+
`maxWidgetHeight` must be tall enough to fit the widest inline layout (the CTA
|
|
73
|
+
button plus the agentic suggestion chips row); content taller than this is
|
|
74
|
+
clipped by the webview.
|
|
75
|
+
|
|
76
|
+
## Events
|
|
77
|
+
|
|
78
|
+
| Name | Payload |
|
|
79
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `onPresentationModeChange` | `(mode: 'widget' \| 'modal', styleId?: string \| null)` — `styleId` identifies which product's flow opened/closed; `null` for changes that did not originate from an open/close event |
|
|
81
|
+
| `onHeightChange` | Current native view height in density-independent points |
|
|
82
|
+
| `onWidgetResult` | `TrueFitWidgetResult` — per-style recommendation state (`styleId`, `isWidgetVisible`, `isRegistered`, `isTraveling`, `isTravelingExternal`, `successType`) |
|
|
83
|
+
| `onAutoSizeSelect` | `{ styleId, size }` — fires when the widget auto-selects a size for a style |
|
|
84
|
+
|
|
85
|
+
`TrueFitWidgetResult.successType` is `'personal'` when the shopper has a
|
|
86
|
+
profile-based recommendation, `'general'` for a generic one, and `null` when
|
|
87
|
+
there is none.
|
|
88
|
+
|
|
89
|
+
## Sign-in popup
|
|
90
|
+
|
|
91
|
+
The widget's account sign-in opens the Keycloak login page via `window.open`.
|
|
92
|
+
The SDK materializes it natively — a `WKWebView` presented as a form sheet on
|
|
93
|
+
iOS, a second `WebView` in a full-screen `Dialog` on Android — sharing the same
|
|
94
|
+
web session (cookies) as the widget so the post-login handshake completes and
|
|
95
|
+
the personalized recommendation appears. No host-app wiring is required.
|
|
96
|
+
|
|
97
|
+
## Architecture
|
|
98
|
+
|
|
99
|
+
This package is a thin adapter. It contains no WebView, modal, JS bridge,
|
|
100
|
+
configuration loader or resize implementation of its own — all of that lives in
|
|
101
|
+
the standalone True Fit native SDKs, which this package depends on:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
React Native SDK (consumer-app-react-native-sdk)
|
|
105
|
+
├── TypeScript public API (src/index.tsx)
|
|
106
|
+
├── Android adapter (ViewManager + prop mappers) → depends on truefit-android-sdk
|
|
107
|
+
└── iOS adapter (RCTViewManager + prop mappers) → depends on TrueFitRecommendation pod
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The adapters only map props to native config, forward native callbacks to JS
|
|
111
|
+
events, and integrate the native view into React Native layout/lifecycle, so the
|
|
112
|
+
widget behaviour matches the standalone native SDK sample apps exactly.
|
|
113
|
+
|
|
114
|
+
## Native SDK dependencies
|
|
115
|
+
|
|
116
|
+
**iOS** — the pod depends on `TrueFitRecommendation`. Your app's Podfile must
|
|
117
|
+
make it resolvable. For local monorepo development, point it at the SDK path:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# ios/Podfile
|
|
121
|
+
pod 'TrueFitRecommendation', :path => '../../ios/ios-sdk'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Once the SDK is published, use the released version instead:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
pod 'TrueFitRecommendation', '~> 2.0.0'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Android** — the adapter depends on the Android SDK. In the monorepo, include
|
|
131
|
+
the SDK as a Gradle project so the adapter picks it up via
|
|
132
|
+
`project(':truefit-android-sdk')`. In your app's `settings.gradle`:
|
|
133
|
+
|
|
134
|
+
```gradle
|
|
135
|
+
include(':truefit-android-sdk')
|
|
136
|
+
project(':truefit-android-sdk').projectDir =
|
|
137
|
+
new File(rootProject.projectDir, '../../android/android-sdk')
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If that project is absent, the adapter falls back to the published artifact
|
|
141
|
+
`com.truefit:truefit-android-sdk:2.0.0` (add the GitHub Pages Maven repo
|
|
142
|
+
`https://true-fit-corp.github.io/truefit-android-maven`).
|
|
143
|
+
|
|
144
|
+
## Integration
|
|
145
|
+
|
|
146
|
+
Install from the local workspace path:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
pnpm add file:../react-native-sdk
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Install iOS pods (after adding the `TrueFitRecommendation` pod line above):
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
cd ios
|
|
156
|
+
pod install
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
React Native autolinking should register both platforms. If your Android app
|
|
160
|
+
does not autolink local packages, add the package manually in
|
|
161
|
+
`MainApplication`:
|
|
162
|
+
|
|
163
|
+
```kotlin
|
|
164
|
+
import com.truefit.reactnative.TrueFitRecommendationPackage
|
|
165
|
+
|
|
166
|
+
override fun getPackages(): List<ReactPackage> =
|
|
167
|
+
PackageList(this).packages.apply {
|
|
168
|
+
add(TrueFitRecommendationPackage())
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Use the view inside any product/detail screen. Give the parent enough width and
|
|
173
|
+
avoid clipping if you want the modal expansion to remain visible:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<View style={{ width: '100%', overflow: 'visible' }}>
|
|
177
|
+
<TrueFitRecommendationView
|
|
178
|
+
config={{
|
|
179
|
+
storeKey: 'your-store-key',
|
|
180
|
+
storeStyle: productStyleId,
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
</View>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Config values must be strings. If your API returns numeric product IDs, convert
|
|
187
|
+
them before passing (e.g. `storeStyle: String(productStyleId)`).
|
|
188
|
+
|
|
189
|
+
## Environments
|
|
190
|
+
|
|
191
|
+
The native SDKs resolve the widget script URL from `config.environment`;
|
|
192
|
+
consumers never pass raw URLs. Supported values are `'prod'` (the default,
|
|
193
|
+
`https://consumer-v2.truefitcorp.com`) and `'development'`
|
|
194
|
+
(`https://consumer-dev.truefitcorp.com`, for testing); both load
|
|
195
|
+
`/tf-integration.js` from that origin. The SDK derives the HTML document's base URL / origin from that URL
|
|
196
|
+
so the module fetch, the widget's `/api` calls, and its cookies are all
|
|
197
|
+
same-origin.
|
|
198
|
+
|
|
199
|
+
## Versioning & release order
|
|
200
|
+
|
|
201
|
+
The React Native adapter depends on the native SDKs, so release them first:
|
|
202
|
+
|
|
203
|
+
1. Publish `truefit-android-sdk` (Maven coordinate `com.truefit:truefit-android-sdk`)
|
|
204
|
+
and `TrueFitRecommendation` (CocoaPods) at the target version.
|
|
205
|
+
2. Bump the matching version constraints in `android/build.gradle` and
|
|
206
|
+
`TrueFitReactNativeWebViewSdk.podspec`.
|
|
207
|
+
3. Publish this React Native package.
|
|
208
|
+
|
|
209
|
+
In the monorepo these steps are unnecessary — the adapter consumes the SDKs
|
|
210
|
+
directly via the Gradle project include and the local pod path above.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'TrueFitReactNativeWebViewSdk'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = {
|
|
10
|
+
:type => 'Commercial',
|
|
11
|
+
:text => 'Copyright © 2026 True Fit Corp. All rights reserved. Use of this SDK is subject to the True Fit SDK License Agreement (EULA): https://www.truefit.com/legal/sdk-license',
|
|
12
|
+
}
|
|
13
|
+
s.authors = { 'True Fit' => 'dev@truefit.com' }
|
|
14
|
+
s.homepage = 'https://www.truefit.com'
|
|
15
|
+
s.platforms = { :ios => '13.0' }
|
|
16
|
+
s.source = { :path => '.' }
|
|
17
|
+
s.source_files = 'ios/**/*.{h,m,mm,swift}'
|
|
18
|
+
s.swift_version = '5.0'
|
|
19
|
+
s.dependency 'React-Core'
|
|
20
|
+
|
|
21
|
+
# The adapter reuses the standalone iOS SDK for all WebView / modal / bridge
|
|
22
|
+
# behaviour. The consuming app's Podfile must make this pod resolvable, e.g.
|
|
23
|
+
# for local monorepo development:
|
|
24
|
+
# pod 'TrueFitRecommendation', :path => '../node_modules/consumer-app-react-native-sdk/../../ios/ios-sdk'
|
|
25
|
+
# or, once published:
|
|
26
|
+
# pod 'TrueFitRecommendation', '~> 2.0.0'
|
|
27
|
+
s.dependency 'TrueFitRecommendation'
|
|
28
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.kotlin_version = '2.0.21'
|
|
3
|
+
repositories {
|
|
4
|
+
google()
|
|
5
|
+
mavenCentral()
|
|
6
|
+
}
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath 'com.android.tools.build:gradle:8.6.1'
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
apply plugin: 'com.android.library'
|
|
14
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
15
|
+
|
|
16
|
+
android {
|
|
17
|
+
namespace 'com.truefit.reactnative'
|
|
18
|
+
compileSdk 35
|
|
19
|
+
|
|
20
|
+
defaultConfig {
|
|
21
|
+
minSdk 23
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
compileOptions {
|
|
25
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
26
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
kotlinOptions {
|
|
30
|
+
jvmTarget = '17'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
repositories {
|
|
35
|
+
google()
|
|
36
|
+
mavenCentral()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation 'com.facebook.react:react-android'
|
|
41
|
+
|
|
42
|
+
// Reuse the standalone Android SDK for all WebView / modal / bridge / resize
|
|
43
|
+
// behaviour. In the monorepo the host app includes the SDK as a Gradle
|
|
44
|
+
// project (:truefit-android-sdk); otherwise the published artifact is used.
|
|
45
|
+
// The SDK brings its own androidx.webkit / androidx.core transitively, so the
|
|
46
|
+
// adapter no longer declares a WebView implementation of its own.
|
|
47
|
+
if (findProject(':truefit-android-sdk') != null) {
|
|
48
|
+
implementation project(':truefit-android-sdk')
|
|
49
|
+
} else {
|
|
50
|
+
implementation 'com.truefit:truefit-android-sdk:2.0.0'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package com.truefit.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class TrueFitRecommendationPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
|
|
10
|
+
|
|
11
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
12
|
+
listOf(TrueFitRecommendationViewManager())
|
|
13
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package com.truefit.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import com.facebook.react.bridge.ReadableType
|
|
5
|
+
import com.truefit.recommendation.TrueFitEnvironment
|
|
6
|
+
import com.truefit.recommendation.TrueFitRecommendationConfig
|
|
7
|
+
import com.truefit.recommendation.TrueFitRecommendationOptions
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Adapter mappers from React Native `ReadableMap` props to the standalone
|
|
11
|
+
* Android SDK's configuration types. The SDK owns the config shape and
|
|
12
|
+
* behaviour; this file only translates the JS props into it.
|
|
13
|
+
*/
|
|
14
|
+
object TrueFitRecommendationProps {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Builds an SDK config from the JS `config` prop, or null when the required
|
|
18
|
+
* fields (storeKey, storeStyle) are absent — the adapter defers loading
|
|
19
|
+
* until they arrive, mirroring the SDK's required-config contract.
|
|
20
|
+
*/
|
|
21
|
+
fun configOrNull(map: ReadableMap?): TrueFitRecommendationConfig? {
|
|
22
|
+
if (map == null) return null
|
|
23
|
+
|
|
24
|
+
val storeKey = map.stringOrNull("storeKey").orEmpty()
|
|
25
|
+
val storeStyle = map.stringOrNull("storeStyle").orEmpty()
|
|
26
|
+
if (storeKey.isBlank() || storeStyle.isBlank()) return null
|
|
27
|
+
|
|
28
|
+
return TrueFitRecommendationConfig(
|
|
29
|
+
storeKey = storeKey,
|
|
30
|
+
storeStyle = storeStyle,
|
|
31
|
+
environment = mapEnvironment(map.stringOrNull("environment")),
|
|
32
|
+
uiLocale = map.stringOrNull("uiLocale")?.takeIf { it.isNotBlank() },
|
|
33
|
+
userToken = map.stringOrNull("userToken")?.takeIf { it.isNotBlank() },
|
|
34
|
+
// A full script URL overrides `environment` (e.g. a built bundle).
|
|
35
|
+
// Raw Vite dev entries (.ts) are not supported by the SDK.
|
|
36
|
+
scriptUrlOverride = map.stringOrNull("scriptUrl")?.takeIf { it.isNotBlank() },
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun options(map: ReadableMap?): TrueFitRecommendationOptions {
|
|
41
|
+
val defaults = TrueFitRecommendationOptions()
|
|
42
|
+
if (map == null) return defaults
|
|
43
|
+
|
|
44
|
+
return TrueFitRecommendationOptions(
|
|
45
|
+
minWidgetHeightDp = map.intOrDefault("minWidgetHeight", defaults.minWidgetHeightDp),
|
|
46
|
+
maxWidgetHeightDp = map.intOrDefault("maxWidgetHeight", defaults.maxWidgetHeightDp),
|
|
47
|
+
modalBackdropOpacity = map.floatOrDefault("modalBackdropOpacity", defaults.modalBackdropOpacity),
|
|
48
|
+
modalTransitionDurationMs = map.longOrDefault(
|
|
49
|
+
"modalTransitionDurationMs",
|
|
50
|
+
defaults.modalTransitionDurationMs,
|
|
51
|
+
),
|
|
52
|
+
disableBackgroundScrollWhenModalOpen = map.booleanOrDefault(
|
|
53
|
+
"disableBackgroundScrollWhenModalOpen",
|
|
54
|
+
defaults.disableBackgroundScrollWhenModalOpen,
|
|
55
|
+
),
|
|
56
|
+
heightAnimationDurationMs = map.longOrDefault(
|
|
57
|
+
"heightAnimationDurationMs",
|
|
58
|
+
defaults.heightAnimationDurationMs,
|
|
59
|
+
),
|
|
60
|
+
debugLogging = map.booleanOrDefault("debugLogging", defaults.debugLogging),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun mapEnvironment(name: String?): TrueFitEnvironment = when (name?.lowercase()) {
|
|
65
|
+
"development", "dev" -> TrueFitEnvironment.Development
|
|
66
|
+
"prod", "production" -> TrueFitEnvironment.Prod
|
|
67
|
+
else -> TrueFitEnvironment.Prod
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun ReadableMap.stringOrNull(name: String): String? {
|
|
72
|
+
if (!hasKey(name) || isNull(name)) return null
|
|
73
|
+
|
|
74
|
+
return when (getType(name)) {
|
|
75
|
+
ReadableType.String -> getString(name)
|
|
76
|
+
ReadableType.Number -> {
|
|
77
|
+
val value = getDouble(name)
|
|
78
|
+
val longValue = value.toLong()
|
|
79
|
+
if (value == longValue.toDouble()) longValue.toString() else value.toString()
|
|
80
|
+
}
|
|
81
|
+
else -> null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private fun ReadableMap.intOrDefault(name: String, defaultValue: Int): Int =
|
|
86
|
+
if (hasKey(name) && !isNull(name)) getDouble(name).toInt() else defaultValue
|
|
87
|
+
|
|
88
|
+
private fun ReadableMap.longOrDefault(name: String, defaultValue: Long): Long =
|
|
89
|
+
if (hasKey(name) && !isNull(name)) getDouble(name).toLong() else defaultValue
|
|
90
|
+
|
|
91
|
+
private fun ReadableMap.floatOrDefault(name: String, defaultValue: Float): Float =
|
|
92
|
+
if (hasKey(name) && !isNull(name)) getDouble(name).toFloat() else defaultValue
|
|
93
|
+
|
|
94
|
+
private fun ReadableMap.booleanOrDefault(name: String, defaultValue: Boolean): Boolean =
|
|
95
|
+
if (hasKey(name) && !isNull(name)) getBoolean(name) else defaultValue
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.truefit.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import com.truefit.recommendation.TrueFitRecommendationView as SdkRecommendationView
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Thin React Native adapter over the standalone Android SDK view
|
|
10
|
+
* ([SdkRecommendationView]). All WebView setup, JS bridge handling, widget and
|
|
11
|
+
* modal rendering, configuration, resize/height handling, navigation, cleanup
|
|
12
|
+
* and the sign-in popup live in the SDK; [sdkView] is instantiated directly
|
|
13
|
+
* from it.
|
|
14
|
+
*
|
|
15
|
+
* This wrapper adds only the React Native-specific layout integration: React
|
|
16
|
+
* Native (both the old and new architecture) skips measure/layout for children
|
|
17
|
+
* that are added or resized natively, so we force a layout pass ourselves.
|
|
18
|
+
* Without this the WebView can render stale or blank after the SDK reparents it
|
|
19
|
+
* between the inline widget and the modal overlay. Because [SdkRecommendationView]
|
|
20
|
+
* bubbles its `requestLayout()` up to this parent, hooking it here covers the
|
|
21
|
+
* reparenting the SDK does internally.
|
|
22
|
+
*/
|
|
23
|
+
class TrueFitRecommendationView(context: Context) : FrameLayout(context) {
|
|
24
|
+
|
|
25
|
+
val sdkView = SdkRecommendationView(context)
|
|
26
|
+
|
|
27
|
+
init {
|
|
28
|
+
// The widget must not be clipped when the SDK animates its height.
|
|
29
|
+
clipChildren = false
|
|
30
|
+
clipToPadding = false
|
|
31
|
+
addView(
|
|
32
|
+
sdkView,
|
|
33
|
+
LayoutParams(
|
|
34
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
35
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private val measureAndLayout = Runnable {
|
|
41
|
+
measure(
|
|
42
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
43
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
|
44
|
+
)
|
|
45
|
+
layout(left, top, right, bottom)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override fun requestLayout() {
|
|
49
|
+
super.requestLayout()
|
|
50
|
+
post(measureAndLayout)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package com.truefit.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactContext
|
|
5
|
+
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.common.MapBuilder
|
|
8
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
11
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
12
|
+
import com.truefit.recommendation.TrueFitRecommendationView.PresentationMode
|
|
13
|
+
|
|
14
|
+
class TrueFitRecommendationViewManager : SimpleViewManager<TrueFitRecommendationView>() {
|
|
15
|
+
override fun getName(): String = REACT_CLASS
|
|
16
|
+
|
|
17
|
+
override fun createViewInstance(reactContext: ThemedReactContext): TrueFitRecommendationView {
|
|
18
|
+
val view = TrueFitRecommendationView(reactContext)
|
|
19
|
+
// React Native lays out in density-independent units; the SDK reports
|
|
20
|
+
// widget height in device pixels, so convert px -> dp here.
|
|
21
|
+
val density = reactContext.resources.displayMetrics.density
|
|
22
|
+
|
|
23
|
+
view.sdkView.onPresentationModeChanged = { mode, styleId ->
|
|
24
|
+
val event = Arguments.createMap().apply {
|
|
25
|
+
putString("mode", if (mode == PresentationMode.Modal) "modal" else "widget")
|
|
26
|
+
if (styleId != null) putString("styleId", styleId) else putNull("styleId")
|
|
27
|
+
}
|
|
28
|
+
emitEvent(view, "topPresentationModeChange", event)
|
|
29
|
+
}
|
|
30
|
+
view.sdkView.onHeightChanged = { heightPx ->
|
|
31
|
+
val event = Arguments.createMap().apply {
|
|
32
|
+
putDouble("height", heightPx / density.toDouble())
|
|
33
|
+
}
|
|
34
|
+
emitEvent(view, "topHeightChange", event)
|
|
35
|
+
}
|
|
36
|
+
view.sdkView.onWidgetResult = { result ->
|
|
37
|
+
val event = Arguments.createMap().apply {
|
|
38
|
+
putString("styleId", result.styleId)
|
|
39
|
+
putBoolean("isWidgetVisible", result.isWidgetVisible)
|
|
40
|
+
putBoolean("isRegistered", result.isRegistered)
|
|
41
|
+
putBoolean("isTraveling", result.isTraveling)
|
|
42
|
+
putBoolean("isTravelingExternal", result.isTravelingExternal)
|
|
43
|
+
if (result.successType != null) putString("successType", result.successType) else putNull("successType")
|
|
44
|
+
}
|
|
45
|
+
emitEvent(view, "topWidgetResult", event)
|
|
46
|
+
}
|
|
47
|
+
view.sdkView.onAutoSizeSelect = { styleId, size ->
|
|
48
|
+
val event = Arguments.createMap().apply {
|
|
49
|
+
putString("styleId", styleId)
|
|
50
|
+
putString("size", size)
|
|
51
|
+
}
|
|
52
|
+
emitEvent(view, "topAutoSizeSelect", event)
|
|
53
|
+
}
|
|
54
|
+
return view
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@ReactProp(name = "config")
|
|
58
|
+
fun setConfig(view: TrueFitRecommendationView, config: ReadableMap?) {
|
|
59
|
+
// Config props arrive incrementally; only load once the required fields
|
|
60
|
+
// are present. The SDK guards against reloading an unchanged config.
|
|
61
|
+
TrueFitRecommendationProps.configOrNull(config)?.let(view.sdkView::load)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@ReactProp(name = "options")
|
|
65
|
+
fun setOptions(view: TrueFitRecommendationView, options: ReadableMap?) {
|
|
66
|
+
view.sdkView.options = TrueFitRecommendationProps.options(options)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun onDropViewInstance(view: TrueFitRecommendationView) {
|
|
70
|
+
view.sdkView.dispose()
|
|
71
|
+
super.onDropViewInstance(view)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> = hashMapOf(
|
|
75
|
+
"topPresentationModeChange" to MapBuilder.of("registrationName", "onPresentationModeChange"),
|
|
76
|
+
"topHeightChange" to MapBuilder.of("registrationName", "onHeightChange"),
|
|
77
|
+
"topWidgetResult" to MapBuilder.of("registrationName", "onWidgetResult"),
|
|
78
|
+
"topAutoSizeSelect" to MapBuilder.of("registrationName", "onAutoSizeSelect"),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
private fun emitEvent(view: TrueFitRecommendationView, eventName: String, payload: WritableMap) {
|
|
82
|
+
val reactContext = view.context as? ReactContext ?: return
|
|
83
|
+
reactContext
|
|
84
|
+
.getJSModule(RCTEventEmitter::class.java)
|
|
85
|
+
.receiveEvent(view.id, eventName, payload)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
companion object {
|
|
89
|
+
const val REACT_CLASS = "TrueFitRecommendationView"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React
|
|
2
|
+
import TrueFitRecommendation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
// Thin React Native adapter over the standalone iOS SDK
|
|
6
|
+
// (`TrueFitRecommendation`). All WebView setup, JS bridge handling, widget and
|
|
7
|
+
// modal rendering, configuration, resize/height handling, navigation, cleanup
|
|
8
|
+
// and the sign-in popup live in the SDK. This class only:
|
|
9
|
+
// - maps React Native `NSDictionary` props to the SDK config/options types,
|
|
10
|
+
// - forwards the SDK's callbacks to React Native `RCTDirectEventBlock`s,
|
|
11
|
+
// - integrates the SDK view into React Native's layout/lifecycle.
|
|
12
|
+
//
|
|
13
|
+
// The class is named distinctly from the SDK's `TrueFitRecommendationView`; the
|
|
14
|
+
// JS component name ("TrueFitRecommendationView") is derived from the view
|
|
15
|
+
// manager, not this class, so the public API is unchanged.
|
|
16
|
+
@objc(TrueFitReactNativeView)
|
|
17
|
+
final class TrueFitReactNativeView: UIView {
|
|
18
|
+
private let recommendationView = TrueFitRecommendationView()
|
|
19
|
+
|
|
20
|
+
// Event callbacks bridged to React Native (see the view manager). styleId
|
|
21
|
+
// identifies which product's flow changed; nil when the change did not
|
|
22
|
+
// originate from an open/close event.
|
|
23
|
+
@objc var onPresentationModeChange: RCTDirectEventBlock?
|
|
24
|
+
@objc var onHeightChange: RCTDirectEventBlock?
|
|
25
|
+
@objc var onWidgetResult: RCTDirectEventBlock?
|
|
26
|
+
@objc var onAutoSizeSelect: RCTDirectEventBlock?
|
|
27
|
+
|
|
28
|
+
@objc var config: NSDictionary? {
|
|
29
|
+
didSet { applyConfig() }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Remapped from the JS `options` prop (see the .m) to avoid colliding with
|
|
33
|
+
// UIView semantics; maps to the SDK's `TrueFitRecommendationOptions`.
|
|
34
|
+
@objc var reactOptions: NSDictionary? {
|
|
35
|
+
didSet {
|
|
36
|
+
recommendationView.options = Self.options(from: reactOptions)
|
|
37
|
+
// Options changed (e.g. min/max height); re-map so a still-valid
|
|
38
|
+
// config reloads through the SDK if needed.
|
|
39
|
+
applyConfig()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override init(frame: CGRect) {
|
|
44
|
+
super.init(frame: frame)
|
|
45
|
+
setup()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
required init?(coder: NSCoder) {
|
|
49
|
+
super.init(coder: coder)
|
|
50
|
+
setup()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The adapter is not part of the SDK view's internal retain cycle
|
|
54
|
+
// (webView configuration -> message handler -> SDK view), so it deinits
|
|
55
|
+
// when React Native releases it on unmount. Disposing here removes the
|
|
56
|
+
// message handler, breaking that cycle so the SDK view is released too.
|
|
57
|
+
deinit {
|
|
58
|
+
recommendationView.dispose()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private func setup() {
|
|
62
|
+
backgroundColor = .clear
|
|
63
|
+
clipsToBounds = false
|
|
64
|
+
|
|
65
|
+
recommendationView.translatesAutoresizingMaskIntoConstraints = false
|
|
66
|
+
addSubview(recommendationView)
|
|
67
|
+
// The SDK view self-sizes and reports its height; React Native drives
|
|
68
|
+
// this container's height from onHeightChange. Pin the SDK view to fill
|
|
69
|
+
// the container so it tracks the React Native-provided frame.
|
|
70
|
+
NSLayoutConstraint.activate([
|
|
71
|
+
recommendationView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
72
|
+
recommendationView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
73
|
+
recommendationView.topAnchor.constraint(equalTo: topAnchor),
|
|
74
|
+
recommendationView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
recommendationView.onPresentationModeChanged = { [weak self] mode, styleId in
|
|
78
|
+
self?.onPresentationModeChange?([
|
|
79
|
+
"mode": mode == .modal ? "modal" : "widget",
|
|
80
|
+
"styleId": styleId ?? NSNull(),
|
|
81
|
+
])
|
|
82
|
+
}
|
|
83
|
+
recommendationView.onHeightChanged = { [weak self] height in
|
|
84
|
+
// iOS measurements are already in points, which are React Native's
|
|
85
|
+
// layout units — no conversion required.
|
|
86
|
+
self?.onHeightChange?(["height": height])
|
|
87
|
+
}
|
|
88
|
+
recommendationView.onWidgetResult = { [weak self] result in
|
|
89
|
+
self?.onWidgetResult?([
|
|
90
|
+
"styleId": result.styleId,
|
|
91
|
+
"isWidgetVisible": result.isWidgetVisible,
|
|
92
|
+
"isRegistered": result.isRegistered,
|
|
93
|
+
"isTraveling": result.isTraveling,
|
|
94
|
+
"isTravelingExternal": result.isTravelingExternal,
|
|
95
|
+
"successType": result.successType ?? NSNull(),
|
|
96
|
+
])
|
|
97
|
+
}
|
|
98
|
+
recommendationView.onAutoSizeSelect = { [weak self] styleId, size in
|
|
99
|
+
self?.onAutoSizeSelect?(["styleId": styleId, "size": size])
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func applyConfig() {
|
|
104
|
+
// Config props arrive incrementally; only load once the required
|
|
105
|
+
// fields are present, mirroring the SDK's required-config contract.
|
|
106
|
+
guard let config,
|
|
107
|
+
let mapped = Self.config(from: config) else { return }
|
|
108
|
+
recommendationView.load(mapped)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// MARK: - Prop mapping
|
|
112
|
+
|
|
113
|
+
private static func config(from dictionary: NSDictionary) -> TrueFitRecommendationConfig? {
|
|
114
|
+
guard let storeKey = string(dictionary["storeKey"]), !storeKey.isEmpty,
|
|
115
|
+
let storeStyle = string(dictionary["storeStyle"]), !storeStyle.isEmpty
|
|
116
|
+
else {
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let environment = string(dictionary["environment"]).flatMap(mapEnvironment) ?? .prod
|
|
121
|
+
let uiLocale = string(dictionary["uiLocale"])?.nonEmpty
|
|
122
|
+
let userToken = string(dictionary["userToken"])?.nonEmpty
|
|
123
|
+
// A full script URL overrides `environment` (e.g. a built bundle). Raw
|
|
124
|
+
// Vite dev entries (.ts) are not supported by the SDK.
|
|
125
|
+
let scriptUrlOverride = string(dictionary["scriptUrl"])?.nonEmpty
|
|
126
|
+
|
|
127
|
+
return TrueFitRecommendationConfig(
|
|
128
|
+
storeKey: storeKey,
|
|
129
|
+
storeStyle: storeStyle,
|
|
130
|
+
environment: environment,
|
|
131
|
+
uiLocale: uiLocale,
|
|
132
|
+
userToken: userToken,
|
|
133
|
+
scriptUrlOverride: scriptUrlOverride
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static func mapEnvironment(_ name: String) -> TrueFitEnvironment {
|
|
138
|
+
switch name.lowercased() {
|
|
139
|
+
case "development", "dev":
|
|
140
|
+
.development
|
|
141
|
+
case "prod", "production":
|
|
142
|
+
.prod
|
|
143
|
+
default:
|
|
144
|
+
.prod
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private static func options(from dictionary: NSDictionary?) -> TrueFitRecommendationOptions {
|
|
149
|
+
var options = TrueFitRecommendationOptions()
|
|
150
|
+
guard let dictionary else { return options }
|
|
151
|
+
|
|
152
|
+
if let value = dictionary["minWidgetHeight"] as? NSNumber {
|
|
153
|
+
options.minWidgetHeight = CGFloat(value.doubleValue)
|
|
154
|
+
}
|
|
155
|
+
if let value = dictionary["maxWidgetHeight"] as? NSNumber {
|
|
156
|
+
options.maxWidgetHeight = CGFloat(value.doubleValue)
|
|
157
|
+
}
|
|
158
|
+
if let value = dictionary["modalBackdropOpacity"] as? NSNumber {
|
|
159
|
+
options.modalBackdropOpacity = CGFloat(value.doubleValue)
|
|
160
|
+
}
|
|
161
|
+
if let durationMs = dictionary["modalTransitionDurationMs"] as? NSNumber {
|
|
162
|
+
options.modalTransitionDuration = durationMs.doubleValue / 1000
|
|
163
|
+
}
|
|
164
|
+
if let value = dictionary["disableBackgroundScrollWhenModalOpen"] as? Bool {
|
|
165
|
+
options.disableBackgroundScrollWhenModalOpen = value
|
|
166
|
+
}
|
|
167
|
+
if let durationMs = dictionary["heightAnimationDurationMs"] as? NSNumber {
|
|
168
|
+
options.heightAnimationDuration = durationMs.doubleValue / 1000
|
|
169
|
+
}
|
|
170
|
+
if let value = dictionary["debugLogging"] as? Bool {
|
|
171
|
+
options.debugLogging = value
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return options
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static func string(_ value: Any?) -> String? {
|
|
178
|
+
if let string = value as? String { return string }
|
|
179
|
+
if let number = value as? NSNumber { return number.stringValue }
|
|
180
|
+
return nil
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private extension String {
|
|
185
|
+
var nonEmpty: String? { isEmpty ? nil : self }
|
|
186
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(TrueFitRecommendationViewManager, RCTViewManager)
|
|
4
|
+
|
|
5
|
+
RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary)
|
|
6
|
+
RCT_REMAP_VIEW_PROPERTY(options, reactOptions, NSDictionary)
|
|
7
|
+
RCT_EXPORT_VIEW_PROPERTY(onPresentationModeChange, RCTDirectEventBlock)
|
|
8
|
+
RCT_EXPORT_VIEW_PROPERTY(onHeightChange, RCTDirectEventBlock)
|
|
9
|
+
RCT_EXPORT_VIEW_PROPERTY(onWidgetResult, RCTDirectEventBlock)
|
|
10
|
+
RCT_EXPORT_VIEW_PROPERTY(onAutoSizeSelect, RCTDirectEventBlock)
|
|
11
|
+
|
|
12
|
+
@end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React
|
|
2
|
+
|
|
3
|
+
@objc(TrueFitRecommendationViewManager)
|
|
4
|
+
final class TrueFitRecommendationViewManager: RCTViewManager {
|
|
5
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
6
|
+
true
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override func view() -> UIView! {
|
|
10
|
+
TrueFitReactNativeView()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.TrueFitRecommendationView = TrueFitRecommendationView;
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _reactNative = require("react-native");
|
|
10
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
|
+
/**
|
|
13
|
+
* Which True Fit environment to point at. The native SDKs resolve the widget
|
|
14
|
+
* script URL from this internally; consumers never pass raw URLs.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Per-style recommendation state emitted by the widget's `widgetResult` event.
|
|
19
|
+
* `successType` is `'personal'` when the shopper has a profile-based
|
|
20
|
+
* recommendation, `'general'` for a generic one, and `null` when there is none.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const NativeTrueFitRecommendationView = (0, _reactNative.requireNativeComponent)('TrueFitRecommendationView');
|
|
24
|
+
const DEFAULT_MIN_HEIGHT = 86;
|
|
25
|
+
function TrueFitRecommendationView({
|
|
26
|
+
config,
|
|
27
|
+
options,
|
|
28
|
+
style,
|
|
29
|
+
onPresentationModeChange,
|
|
30
|
+
onHeightChange,
|
|
31
|
+
onWidgetResult,
|
|
32
|
+
onAutoSizeSelect
|
|
33
|
+
}) {
|
|
34
|
+
const minHeight = options?.minWidgetHeight ?? DEFAULT_MIN_HEIGHT;
|
|
35
|
+
const [measuredHeight, setMeasuredHeight] = (0, _react.useState)(minHeight);
|
|
36
|
+
const [presentationMode, setPresentationMode] = (0, _react.useState)('widget');
|
|
37
|
+
const handleHeightChange = (0, _react.useCallback)(event => {
|
|
38
|
+
const nextHeight = Math.max(event.nativeEvent.height, minHeight);
|
|
39
|
+
setMeasuredHeight(nextHeight);
|
|
40
|
+
onHeightChange?.(nextHeight);
|
|
41
|
+
}, [minHeight, onHeightChange]);
|
|
42
|
+
const handlePresentationModeChange = (0, _react.useCallback)(event => {
|
|
43
|
+
const nextMode = event.nativeEvent.mode;
|
|
44
|
+
setPresentationMode(nextMode);
|
|
45
|
+
onPresentationModeChange?.(nextMode, event.nativeEvent.styleId ?? null);
|
|
46
|
+
}, [onPresentationModeChange]);
|
|
47
|
+
const handleWidgetResult = (0, _react.useCallback)(event => {
|
|
48
|
+
onWidgetResult?.(event.nativeEvent);
|
|
49
|
+
}, [onWidgetResult]);
|
|
50
|
+
const handleAutoSizeSelect = (0, _react.useCallback)(event => {
|
|
51
|
+
onAutoSizeSelect?.(event.nativeEvent);
|
|
52
|
+
}, [onAutoSizeSelect]);
|
|
53
|
+
const containerStyle = (0, _react.useMemo)(() => [styles.container, style, {
|
|
54
|
+
height: measuredHeight,
|
|
55
|
+
minHeight,
|
|
56
|
+
zIndex: presentationMode === 'modal' ? 1000 : undefined,
|
|
57
|
+
elevation: presentationMode === 'modal' ? 16 : undefined
|
|
58
|
+
}], [measuredHeight, minHeight, presentationMode, style]);
|
|
59
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
60
|
+
style: containerStyle,
|
|
61
|
+
collapsable: false,
|
|
62
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(NativeTrueFitRecommendationView, {
|
|
63
|
+
config: config,
|
|
64
|
+
options: options,
|
|
65
|
+
style: styles.nativeView,
|
|
66
|
+
onHeightChange: handleHeightChange,
|
|
67
|
+
onPresentationModeChange: handlePresentationModeChange,
|
|
68
|
+
onWidgetResult: handleWidgetResult,
|
|
69
|
+
onAutoSizeSelect: handleAutoSizeSelect
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const styles = _reactNative.StyleSheet.create({
|
|
74
|
+
container: {
|
|
75
|
+
width: '100%',
|
|
76
|
+
overflow: 'visible',
|
|
77
|
+
position: 'relative'
|
|
78
|
+
},
|
|
79
|
+
nativeView: {
|
|
80
|
+
width: '100%',
|
|
81
|
+
height: '100%'
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
var _default = exports.default = TrueFitRecommendationView;
|
|
85
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","NativeTrueFitRecommendationView","requireNativeComponent","DEFAULT_MIN_HEIGHT","TrueFitRecommendationView","config","options","style","onPresentationModeChange","onHeightChange","onWidgetResult","onAutoSizeSelect","minHeight","minWidgetHeight","measuredHeight","setMeasuredHeight","useState","presentationMode","setPresentationMode","handleHeightChange","useCallback","event","nextHeight","Math","max","nativeEvent","height","handlePresentationModeChange","nextMode","mode","styleId","handleWidgetResult","handleAutoSizeSelect","containerStyle","useMemo","styles","container","zIndex","undefined","elevation","jsx","View","collapsable","children","nativeView","StyleSheet","create","width","overflow","position","_default","exports"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { requireNativeComponent, StyleSheet, View } from 'react-native';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Which True Fit environment to point at. The native SDKs resolve the widget
|
|
8
|
+
* script URL from this internally; consumers never pass raw URLs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-style recommendation state emitted by the widget's `widgetResult` event.
|
|
13
|
+
* `successType` is `'personal'` when the shopper has a profile-based
|
|
14
|
+
* recommendation, `'general'` for a generic one, and `null` when there is none.
|
|
15
|
+
*/
|
|
16
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
17
|
+
const NativeTrueFitRecommendationView = requireNativeComponent('TrueFitRecommendationView');
|
|
18
|
+
const DEFAULT_MIN_HEIGHT = 86;
|
|
19
|
+
export function TrueFitRecommendationView({
|
|
20
|
+
config,
|
|
21
|
+
options,
|
|
22
|
+
style,
|
|
23
|
+
onPresentationModeChange,
|
|
24
|
+
onHeightChange,
|
|
25
|
+
onWidgetResult,
|
|
26
|
+
onAutoSizeSelect
|
|
27
|
+
}) {
|
|
28
|
+
const minHeight = options?.minWidgetHeight ?? DEFAULT_MIN_HEIGHT;
|
|
29
|
+
const [measuredHeight, setMeasuredHeight] = useState(minHeight);
|
|
30
|
+
const [presentationMode, setPresentationMode] = useState('widget');
|
|
31
|
+
const handleHeightChange = useCallback(event => {
|
|
32
|
+
const nextHeight = Math.max(event.nativeEvent.height, minHeight);
|
|
33
|
+
setMeasuredHeight(nextHeight);
|
|
34
|
+
onHeightChange?.(nextHeight);
|
|
35
|
+
}, [minHeight, onHeightChange]);
|
|
36
|
+
const handlePresentationModeChange = useCallback(event => {
|
|
37
|
+
const nextMode = event.nativeEvent.mode;
|
|
38
|
+
setPresentationMode(nextMode);
|
|
39
|
+
onPresentationModeChange?.(nextMode, event.nativeEvent.styleId ?? null);
|
|
40
|
+
}, [onPresentationModeChange]);
|
|
41
|
+
const handleWidgetResult = useCallback(event => {
|
|
42
|
+
onWidgetResult?.(event.nativeEvent);
|
|
43
|
+
}, [onWidgetResult]);
|
|
44
|
+
const handleAutoSizeSelect = useCallback(event => {
|
|
45
|
+
onAutoSizeSelect?.(event.nativeEvent);
|
|
46
|
+
}, [onAutoSizeSelect]);
|
|
47
|
+
const containerStyle = useMemo(() => [styles.container, style, {
|
|
48
|
+
height: measuredHeight,
|
|
49
|
+
minHeight,
|
|
50
|
+
zIndex: presentationMode === 'modal' ? 1000 : undefined,
|
|
51
|
+
elevation: presentationMode === 'modal' ? 16 : undefined
|
|
52
|
+
}], [measuredHeight, minHeight, presentationMode, style]);
|
|
53
|
+
return /*#__PURE__*/_jsx(View, {
|
|
54
|
+
style: containerStyle,
|
|
55
|
+
collapsable: false,
|
|
56
|
+
children: /*#__PURE__*/_jsx(NativeTrueFitRecommendationView, {
|
|
57
|
+
config: config,
|
|
58
|
+
options: options,
|
|
59
|
+
style: styles.nativeView,
|
|
60
|
+
onHeightChange: handleHeightChange,
|
|
61
|
+
onPresentationModeChange: handlePresentationModeChange,
|
|
62
|
+
onWidgetResult: handleWidgetResult,
|
|
63
|
+
onAutoSizeSelect: handleAutoSizeSelect
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const styles = StyleSheet.create({
|
|
68
|
+
container: {
|
|
69
|
+
width: '100%',
|
|
70
|
+
overflow: 'visible',
|
|
71
|
+
position: 'relative'
|
|
72
|
+
},
|
|
73
|
+
nativeView: {
|
|
74
|
+
width: '100%',
|
|
75
|
+
height: '100%'
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
export default TrueFitRecommendationView;
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useCallback","useMemo","useState","requireNativeComponent","StyleSheet","View","jsx","_jsx","NativeTrueFitRecommendationView","DEFAULT_MIN_HEIGHT","TrueFitRecommendationView","config","options","style","onPresentationModeChange","onHeightChange","onWidgetResult","onAutoSizeSelect","minHeight","minWidgetHeight","measuredHeight","setMeasuredHeight","presentationMode","setPresentationMode","handleHeightChange","event","nextHeight","Math","max","nativeEvent","height","handlePresentationModeChange","nextMode","mode","styleId","handleWidgetResult","handleAutoSizeSelect","containerStyle","styles","container","zIndex","undefined","elevation","collapsable","children","nativeView","create","width","overflow","position"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
/**
|
|
4
|
+
* Which True Fit environment to point at. The native SDKs resolve the widget
|
|
5
|
+
* script URL from this internally; consumers never pass raw URLs.
|
|
6
|
+
*/
|
|
7
|
+
export type TrueFitEnvironment = 'development' | 'prod';
|
|
8
|
+
export type TrueFitRecommendationConfig = {
|
|
9
|
+
environment?: TrueFitEnvironment;
|
|
10
|
+
storeKey?: string;
|
|
11
|
+
storeStyle?: string;
|
|
12
|
+
uiLocale?: string;
|
|
13
|
+
/** Connect user token to seed into the Android widget cookie store. */
|
|
14
|
+
userToken?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Full widget script URL that overrides `environment` (e.g. a built bundle).
|
|
17
|
+
* The WebView origin is derived from it. Raw Vite dev entries (`.ts`) are not
|
|
18
|
+
* supported.
|
|
19
|
+
*/
|
|
20
|
+
scriptUrl?: string;
|
|
21
|
+
};
|
|
22
|
+
export type TrueFitRecommendationOptions = {
|
|
23
|
+
minWidgetHeight?: number;
|
|
24
|
+
maxWidgetHeight?: number;
|
|
25
|
+
modalBackdropOpacity?: number;
|
|
26
|
+
modalTransitionDurationMs?: number;
|
|
27
|
+
disableBackgroundScrollWhenModalOpen?: boolean;
|
|
28
|
+
heightAnimationDurationMs?: number;
|
|
29
|
+
debugLogging?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type TrueFitPresentationMode = 'widget' | 'modal';
|
|
32
|
+
/**
|
|
33
|
+
* Per-style recommendation state emitted by the widget's `widgetResult` event.
|
|
34
|
+
* `successType` is `'personal'` when the shopper has a profile-based
|
|
35
|
+
* recommendation, `'general'` for a generic one, and `null` when there is none.
|
|
36
|
+
*/
|
|
37
|
+
export type TrueFitWidgetResult = {
|
|
38
|
+
styleId: string;
|
|
39
|
+
isWidgetVisible: boolean;
|
|
40
|
+
isRegistered: boolean;
|
|
41
|
+
isTraveling: boolean;
|
|
42
|
+
isTravelingExternal: boolean;
|
|
43
|
+
successType: string | null;
|
|
44
|
+
};
|
|
45
|
+
export type TrueFitAutoSizeSelect = {
|
|
46
|
+
styleId: string;
|
|
47
|
+
size: string;
|
|
48
|
+
};
|
|
49
|
+
export type TrueFitRecommendationViewProps = {
|
|
50
|
+
config?: TrueFitRecommendationConfig;
|
|
51
|
+
options?: TrueFitRecommendationOptions;
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
onPresentationModeChange?: (mode: TrueFitPresentationMode, styleId?: string | null) => void;
|
|
54
|
+
onHeightChange?: (height: number) => void;
|
|
55
|
+
onWidgetResult?: (result: TrueFitWidgetResult) => void;
|
|
56
|
+
onAutoSizeSelect?: (selection: TrueFitAutoSizeSelect) => void;
|
|
57
|
+
};
|
|
58
|
+
export declare function TrueFitRecommendationView({ config, options, style, onPresentationModeChange, onHeightChange, onWidgetResult, onAutoSizeSelect, }: TrueFitRecommendationViewProps): React.JSX.Element;
|
|
59
|
+
export default TrueFitRecommendationView;
|
|
60
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EAGL,SAAS,EAGT,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,oCAAoC,CAAC,EAAE,OAAO,CAAC;IAC/C,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAqBF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,OAAO,CAAC,EAAE,4BAA4B,CAAC;IACvC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,uBAAuB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5F,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC/D,CAAC;AAQF,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,OAAO,EACP,KAAK,EACL,wBAAwB,EACxB,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,EAAE,8BAA8B,qBAgEhC;AAcD,eAAe,yBAAyB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tfcorp/consumer-app-react-native-sdk",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "React Native bridge for the True Fit recommendation WebView SDK.",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "lib/module/index.js",
|
|
9
|
+
"source": "src/index.tsx",
|
|
10
|
+
"homepage": "https://www.truefit.com",
|
|
11
|
+
"author": "True Fit <dev@truefit.com>",
|
|
12
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
13
|
+
"files": [
|
|
14
|
+
"lib",
|
|
15
|
+
"android",
|
|
16
|
+
"ios",
|
|
17
|
+
"*.podspec",
|
|
18
|
+
"react-native.config.js",
|
|
19
|
+
"!**/build",
|
|
20
|
+
"!**/__tests__",
|
|
21
|
+
"!**/.gradle",
|
|
22
|
+
"!**/*.tsbuildinfo"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "*",
|
|
26
|
+
"react-native": "*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^18.2.0",
|
|
30
|
+
"del-cli": "^5.1.0",
|
|
31
|
+
"react": "18.2.0",
|
|
32
|
+
"react-native": "0.74.5",
|
|
33
|
+
"react-native-builder-bob": "^0.30.0",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"react-native-builder-bob": {
|
|
37
|
+
"source": "src",
|
|
38
|
+
"output": "lib",
|
|
39
|
+
"targets": [
|
|
40
|
+
"commonjs",
|
|
41
|
+
"module",
|
|
42
|
+
"typescript"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"clean": "del-cli lib",
|
|
48
|
+
"build": "bob build"
|
|
49
|
+
}
|
|
50
|
+
}
|