@whop/elements-react-native 0.1.0
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/README.md +179 -0
- package/android/build.gradle +64 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/java/com/whop/elements/WhopAddressSuggestionsModule.kt +102 -0
- package/android/src/main/java/com/whop/elements/WhopAuthSessionModule.kt +126 -0
- package/android/src/main/java/com/whop/elements/WhopElementsPackage.kt +39 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonManager.kt +74 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonPressEvent.kt +17 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonView.kt +178 -0
- package/android/src/main/java/com/whop/elements/WhopPlatformPayModule.kt +212 -0
- package/assets/whop-wordmark-dark.png +0 -0
- package/assets/whop-wordmark-dark@2x.png +0 -0
- package/assets/whop-wordmark-dark@3x.png +0 -0
- package/assets/whop-wordmark-light.png +0 -0
- package/assets/whop-wordmark-light@2x.png +0 -0
- package/assets/whop-wordmark-light@3x.png +0 -0
- package/ios/Package.swift +59 -0
- package/ios/Sources/ElementsReactNative/WhopAddressSuggestionsImpl.swift +189 -0
- package/ios/Sources/ElementsReactNative/WhopAuthSessionImpl.swift +106 -0
- package/ios/Sources/ElementsReactNative/WhopPayButtonImpl.swift +89 -0
- package/ios/Sources/ElementsReactNative/WhopPlatformPayImpl.swift +181 -0
- package/ios/generated/WhopAddressSuggestionsModule.h +11 -0
- package/ios/generated/WhopAddressSuggestionsModule.mm +46 -0
- package/ios/generated/WhopAuthSessionModule.h +11 -0
- package/ios/generated/WhopAuthSessionModule.mm +45 -0
- package/ios/generated/WhopElementsShims.h +35 -0
- package/ios/generated/WhopPayButtonComponentView.h +12 -0
- package/ios/generated/WhopPayButtonComponentView.mm +82 -0
- package/ios/generated/WhopPlatformPayModule.h +11 -0
- package/ios/generated/WhopPlatformPayModule.mm +51 -0
- package/lib/module/api/client.js +139 -0
- package/lib/module/api/client.js.map +1 -0
- package/lib/module/api/client.test.js +137 -0
- package/lib/module/api/client.test.js.map +1 -0
- package/lib/module/api/types.js +2 -0
- package/lib/module/api/types.js.map +1 -0
- package/lib/module/controller/actions.js +148 -0
- package/lib/module/controller/actions.js.map +1 -0
- package/lib/module/controller/actions.test.js +102 -0
- package/lib/module/controller/actions.test.js.map +1 -0
- package/lib/module/controller/store.js +158 -0
- package/lib/module/controller/store.js.map +1 -0
- package/lib/module/core/address/country-data.js +1370 -0
- package/lib/module/core/address/country-data.js.map +1 -0
- package/lib/module/core/address/country-name.js +38 -0
- package/lib/module/core/address/country-name.js.map +1 -0
- package/lib/module/core/address/country-names.generated.js +249 -0
- package/lib/module/core/address/country-names.generated.js.map +1 -0
- package/lib/module/core/address/engine.js +245 -0
- package/lib/module/core/address/engine.js.map +1 -0
- package/lib/module/core/address/engine.test.js +216 -0
- package/lib/module/core/address/engine.test.js.map +1 -0
- package/lib/module/core/address/form-state.js +409 -0
- package/lib/module/core/address/form-state.js.map +1 -0
- package/lib/module/core/address/form-state.test.js +516 -0
- package/lib/module/core/address/form-state.test.js.map +1 -0
- package/lib/module/core/address/places.js +36 -0
- package/lib/module/core/address/places.js.map +1 -0
- package/lib/module/core/address/required-coverage.test.js +57 -0
- package/lib/module/core/address/required-coverage.test.js.map +1 -0
- package/lib/module/core/address/suggest.js +102 -0
- package/lib/module/core/address/suggest.js.map +1 -0
- package/lib/module/core/address/suggest.test.js +156 -0
- package/lib/module/core/address/suggest.test.js.map +1 -0
- package/lib/module/core/address/use-suggestions.js +33 -0
- package/lib/module/core/address/use-suggestions.js.map +1 -0
- package/lib/module/core/payments/address-change.js +91 -0
- package/lib/module/core/payments/address-change.js.map +1 -0
- package/lib/module/core/payments/billing-merge.js +62 -0
- package/lib/module/core/payments/billing-merge.js.map +1 -0
- package/lib/module/core/payments/browser-info.js +42 -0
- package/lib/module/core/payments/browser-info.js.map +1 -0
- package/lib/module/core/payments/bt.js +151 -0
- package/lib/module/core/payments/bt.js.map +1 -0
- package/lib/module/core/payments/bt.test.js +23 -0
- package/lib/module/core/payments/bt.test.js.map +1 -0
- package/lib/module/core/payments/currency.js +37 -0
- package/lib/module/core/payments/currency.js.map +1 -0
- package/lib/module/core/payments/detail.js +159 -0
- package/lib/module/core/payments/detail.js.map +1 -0
- package/lib/module/core/payments/detail.test.js +225 -0
- package/lib/module/core/payments/detail.test.js.map +1 -0
- package/lib/module/core/payments/errors.js +115 -0
- package/lib/module/core/payments/errors.js.map +1 -0
- package/lib/module/core/payments/mint.js +89 -0
- package/lib/module/core/payments/mint.js.map +1 -0
- package/lib/module/core/payments/plan.js +131 -0
- package/lib/module/core/payments/plan.js.map +1 -0
- package/lib/module/core/payments/refusals.js +17 -0
- package/lib/module/core/payments/refusals.js.map +1 -0
- package/lib/module/core/payments/secure-fields.js +87 -0
- package/lib/module/core/payments/secure-fields.js.map +1 -0
- package/lib/module/core/payments/tax-id-catalog.js +616 -0
- package/lib/module/core/payments/tax-id-catalog.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.js +42 -0
- package/lib/module/core/payments/wallet-contact.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.test.js +77 -0
- package/lib/module/core/payments/wallet-contact.test.js.map +1 -0
- package/lib/module/elements/address.js +467 -0
- package/lib/module/elements/address.js.map +1 -0
- package/lib/module/elements/branding.js +114 -0
- package/lib/module/elements/branding.js.map +1 -0
- package/lib/module/elements/card-fields/context.js +254 -0
- package/lib/module/elements/card-fields/context.js.map +1 -0
- package/lib/module/elements/card-fields/fields.js +309 -0
- package/lib/module/elements/card-fields/fields.js.map +1 -0
- package/lib/module/elements/card-fields/index.js +30 -0
- package/lib/module/elements/card-fields/index.js.map +1 -0
- package/lib/module/elements/card.js +107 -0
- package/lib/module/elements/card.js.map +1 -0
- package/lib/module/elements/email.js +84 -0
- package/lib/module/elements/email.js.map +1 -0
- package/lib/module/elements/payment/declared-fields.js +186 -0
- package/lib/module/elements/payment/declared-fields.js.map +1 -0
- package/lib/module/elements/payment/index.js +448 -0
- package/lib/module/elements/payment/index.js.map +1 -0
- package/lib/module/elements/payment/wallet.js +132 -0
- package/lib/module/elements/payment/wallet.js.map +1 -0
- package/lib/module/elements/tax-id.js +100 -0
- package/lib/module/elements/tax-id.js.map +1 -0
- package/lib/module/i18n/catalogs.js +103 -0
- package/lib/module/i18n/catalogs.js.map +1 -0
- package/lib/module/i18n/index.js +45 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/index.js +27 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/namespace.js +174 -0
- package/lib/module/namespace.js.map +1 -0
- package/lib/module/native/index.js +128 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/provider.js +56 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js +6 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js.map +1 -0
- package/lib/module/specs/NativeWhopAuthSession.js +10 -0
- package/lib/module/specs/NativeWhopAuthSession.js.map +1 -0
- package/lib/module/specs/NativeWhopPlatformPay.js +10 -0
- package/lib/module/specs/NativeWhopPlatformPay.js.map +1 -0
- package/lib/module/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/lib/module/theme/appearance.js +85 -0
- package/lib/module/theme/appearance.js.map +1 -0
- package/lib/module/theme/appearance.test.js +124 -0
- package/lib/module/theme/appearance.test.js.map +1 -0
- package/lib/module/theme/provider.js +25 -0
- package/lib/module/theme/provider.js.map +1 -0
- package/lib/module/theme/resolve.js +69 -0
- package/lib/module/theme/resolve.js.map +1 -0
- package/lib/module/theme/scales.generated.js +419 -0
- package/lib/module/theme/scales.generated.js.map +1 -0
- package/lib/module/ui/element-frame.js +83 -0
- package/lib/module/ui/element-frame.js.map +1 -0
- package/lib/module/ui/payment-method-icon.js +69 -0
- package/lib/module/ui/payment-method-icon.js.map +1 -0
- package/lib/module/ui/primitives.js +266 -0
- package/lib/module/ui/primitives.js.map +1 -0
- package/lib/module/ui/select.js +281 -0
- package/lib/module/ui/select.js.map +1 -0
- package/lib/module/ui/suggestions.js +71 -0
- package/lib/module/ui/suggestions.js.map +1 -0
- package/lib/module/ui/whop-logo.js +42 -0
- package/lib/module/ui/whop-logo.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/api/client.d.ts +87 -0
- package/lib/typescript/src/api/client.d.ts.map +1 -0
- package/lib/typescript/src/api/types.d.ts +175 -0
- package/lib/typescript/src/api/types.d.ts.map +1 -0
- package/lib/typescript/src/controller/actions.d.ts +42 -0
- package/lib/typescript/src/controller/actions.d.ts.map +1 -0
- package/lib/typescript/src/controller/store.d.ts +90 -0
- package/lib/typescript/src/controller/store.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-data.d.ts +4 -0
- package/lib/typescript/src/core/address/country-data.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-name.d.ts +2 -0
- package/lib/typescript/src/core/address/country-name.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts +2 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts.map +1 -0
- package/lib/typescript/src/core/address/engine.d.ts +97 -0
- package/lib/typescript/src/core/address/engine.d.ts.map +1 -0
- package/lib/typescript/src/core/address/form-state.d.ts +144 -0
- package/lib/typescript/src/core/address/form-state.d.ts.map +1 -0
- package/lib/typescript/src/core/address/places.d.ts +23 -0
- package/lib/typescript/src/core/address/places.d.ts.map +1 -0
- package/lib/typescript/src/core/address/suggest.d.ts +47 -0
- package/lib/typescript/src/core/address/suggest.d.ts.map +1 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts +3 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/address-change.d.ts +52 -0
- package/lib/typescript/src/core/payments/address-change.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts +39 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts +12 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/bt.d.ts +55 -0
- package/lib/typescript/src/core/payments/bt.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/currency.d.ts +7 -0
- package/lib/typescript/src/core/payments/currency.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/detail.d.ts +53 -0
- package/lib/typescript/src/core/payments/detail.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/errors.d.ts +62 -0
- package/lib/typescript/src/core/payments/errors.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/mint.d.ts +74 -0
- package/lib/typescript/src/core/payments/mint.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/plan.d.ts +69 -0
- package/lib/typescript/src/core/payments/plan.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/refusals.d.ts +12 -0
- package/lib/typescript/src/core/payments/refusals.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts +33 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts +481 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts +40 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts.map +1 -0
- package/lib/typescript/src/elements/address.d.ts +55 -0
- package/lib/typescript/src/elements/address.d.ts.map +1 -0
- package/lib/typescript/src/elements/branding.d.ts +18 -0
- package/lib/typescript/src/elements/branding.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts +67 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts +15 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts +21 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/card.d.ts +21 -0
- package/lib/typescript/src/elements/card.d.ts.map +1 -0
- package/lib/typescript/src/elements/email.d.ts +31 -0
- package/lib/typescript/src/elements/email.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts +43 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/index.d.ts +37 -0
- package/lib/typescript/src/elements/payment/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts +39 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts.map +1 -0
- package/lib/typescript/src/elements/tax-id.d.ts +41 -0
- package/lib/typescript/src/elements/tax-id.d.ts.map +1 -0
- package/lib/typescript/src/i18n/catalogs.d.ts +7 -0
- package/lib/typescript/src/i18n/catalogs.d.ts.map +1 -0
- package/lib/typescript/src/i18n/index.d.ts +17 -0
- package/lib/typescript/src/i18n/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +42 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/namespace.d.ts +36 -0
- package/lib/typescript/src/namespace.d.ts.map +1 -0
- package/lib/typescript/src/native/index.d.ts +62 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/provider.d.ts +37 -0
- package/lib/typescript/src/provider.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts +13 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts +20 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts +24 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts.map +1 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts +21 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/theme/appearance.d.ts +71 -0
- package/lib/typescript/src/theme/appearance.d.ts.map +1 -0
- package/lib/typescript/src/theme/provider.d.ts +11 -0
- package/lib/typescript/src/theme/provider.d.ts.map +1 -0
- package/lib/typescript/src/theme/resolve.d.ts +19 -0
- package/lib/typescript/src/theme/resolve.d.ts.map +1 -0
- package/lib/typescript/src/theme/scales.generated.d.ts +18 -0
- package/lib/typescript/src/theme/scales.generated.d.ts.map +1 -0
- package/lib/typescript/src/ui/element-frame.d.ts +32 -0
- package/lib/typescript/src/ui/element-frame.d.ts.map +1 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts +21 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts.map +1 -0
- package/lib/typescript/src/ui/primitives.d.ts +94 -0
- package/lib/typescript/src/ui/primitives.d.ts.map +1 -0
- package/lib/typescript/src/ui/select.d.ts +25 -0
- package/lib/typescript/src/ui/select.d.ts.map +1 -0
- package/lib/typescript/src/ui/suggestions.d.ts +15 -0
- package/lib/typescript/src/ui/suggestions.d.ts.map +1 -0
- package/lib/typescript/src/ui/whop-logo.d.ts +4 -0
- package/lib/typescript/src/ui/whop-logo.d.ts.map +1 -0
- package/package.json +121 -0
- package/scripts/check-swift.sh +27 -0
- package/scripts/codegen-shims.mjs +409 -0
- package/scripts/gen-country-names.mjs +58 -0
- package/scripts/gen-theme.mjs +124 -0
- package/scripts/gen-wordmark.mjs +68 -0
- package/scripts/mirror.mjs +112 -0
- package/src/api/client.ts +202 -0
- package/src/api/types.ts +166 -0
- package/src/controller/actions.ts +197 -0
- package/src/controller/store.ts +201 -0
- package/src/core/address/country-data.ts +927 -0
- package/src/core/address/country-name.ts +36 -0
- package/src/core/address/country-names.generated.ts +246 -0
- package/src/core/address/engine.ts +349 -0
- package/src/core/address/form-state.ts +479 -0
- package/src/core/address/places.ts +39 -0
- package/src/core/address/suggest.ts +153 -0
- package/src/core/address/use-suggestions.ts +37 -0
- package/src/core/payments/address-change.ts +121 -0
- package/src/core/payments/billing-merge.ts +91 -0
- package/src/core/payments/browser-info.ts +43 -0
- package/src/core/payments/bt.ts +176 -0
- package/src/core/payments/currency.ts +54 -0
- package/src/core/payments/detail.ts +204 -0
- package/src/core/payments/errors.ts +171 -0
- package/src/core/payments/mint.ts +130 -0
- package/src/core/payments/plan.ts +198 -0
- package/src/core/payments/refusals.ts +24 -0
- package/src/core/payments/secure-fields.ts +85 -0
- package/src/core/payments/tax-id-catalog.ts +320 -0
- package/src/core/payments/wallet-contact.ts +56 -0
- package/src/elements/address.tsx +570 -0
- package/src/elements/branding.tsx +87 -0
- package/src/elements/card-fields/context.tsx +310 -0
- package/src/elements/card-fields/fields.tsx +281 -0
- package/src/elements/card-fields/index.tsx +37 -0
- package/src/elements/card.tsx +101 -0
- package/src/elements/email.tsx +100 -0
- package/src/elements/payment/declared-fields.tsx +238 -0
- package/src/elements/payment/index.tsx +497 -0
- package/src/elements/payment/wallet.tsx +155 -0
- package/src/elements/tax-id.tsx +143 -0
- package/src/i18n/catalogs.ts +100 -0
- package/src/i18n/index.tsx +46 -0
- package/src/index.ts +68 -0
- package/src/namespace.tsx +231 -0
- package/src/native/index.ts +173 -0
- package/src/provider.tsx +76 -0
- package/src/specs/NativeWhopAddressSuggestions.ts +14 -0
- package/src/specs/NativeWhopAuthSession.ts +21 -0
- package/src/specs/NativeWhopPlatformPay.ts +25 -0
- package/src/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/src/theme/appearance.ts +150 -0
- package/src/theme/provider.tsx +28 -0
- package/src/theme/resolve.ts +68 -0
- package/src/theme/scales.generated.ts +427 -0
- package/src/ui/element-frame.tsx +94 -0
- package/src/ui/payment-method-icon.tsx +55 -0
- package/src/ui/primitives.tsx +269 -0
- package/src/ui/select.tsx +255 -0
- package/src/ui/suggestions.tsx +69 -0
- package/src/ui/whop-logo.tsx +37 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
defaultTaxIdType,
|
|
6
|
+
isTaxIdType,
|
|
7
|
+
TAX_ID_TYPE_META,
|
|
8
|
+
TAX_ID_TYPES,
|
|
9
|
+
type TaxIdType,
|
|
10
|
+
useTaxIdTypeLabels,
|
|
11
|
+
} from '../core/payments/tax-id-catalog';
|
|
12
|
+
import { useT } from '../i18n';
|
|
13
|
+
import { ElementFrame } from '../ui/element-frame';
|
|
14
|
+
import { Field, FieldSkeleton, Stack } from '../ui/primitives';
|
|
15
|
+
import { Select } from '../ui/select';
|
|
16
|
+
|
|
17
|
+
export interface TaxIdChangePayload {
|
|
18
|
+
taxId: { type: TaxIdType; value: string };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TaxIdElementProps {
|
|
22
|
+
/**
|
|
23
|
+
* ISO 3166-1 alpha-2 buyer country used to preselect the type. `AR` selects `ar_cuit`. EU and
|
|
24
|
+
* unmapped or empty values select `eu_vat`. A manual selection persists across country changes.
|
|
25
|
+
*/
|
|
26
|
+
country?: string;
|
|
27
|
+
/** Initial `type` and `value` from a resumed session. An unsupported type falls back. */
|
|
28
|
+
defaultValue?: { type: string; value: string };
|
|
29
|
+
/** Disables both fields while the host saves the last committed registration. */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** API validation error shown below the value input. */
|
|
32
|
+
error?: string;
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
fallback?: ReactNode;
|
|
35
|
+
onChange?: (payload: TaxIdChangePayload) => void;
|
|
36
|
+
onReady?: () => void;
|
|
37
|
+
onError?: (error: { message: string; code?: string }) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Collects a business tax registration. `onChange` emits committed pairs only — on blur or submit
|
|
42
|
+
* with a non-empty value, or on a type change while a value is present. Identical consecutive
|
|
43
|
+
* values fire once, and clearing the value does not fire, so the host must clear stored
|
|
44
|
+
* registrations itself.
|
|
45
|
+
*/
|
|
46
|
+
export function TaxIdElement({
|
|
47
|
+
country = '',
|
|
48
|
+
defaultValue,
|
|
49
|
+
disabled = false,
|
|
50
|
+
error,
|
|
51
|
+
style,
|
|
52
|
+
fallback,
|
|
53
|
+
onChange,
|
|
54
|
+
onReady,
|
|
55
|
+
onError,
|
|
56
|
+
}: TaxIdElementProps) {
|
|
57
|
+
const t = useT();
|
|
58
|
+
const labels = useTaxIdTypeLabels();
|
|
59
|
+
|
|
60
|
+
const seededType = defaultValue && isTaxIdType(defaultValue.type) ? defaultValue.type : undefined;
|
|
61
|
+
const [type, setType] = useState<TaxIdType>(() => seededType ?? defaultTaxIdType(country));
|
|
62
|
+
const [value, setValue] = useState(() => defaultValue?.value ?? '');
|
|
63
|
+
const manuallyPicked = useRef(Boolean(seededType));
|
|
64
|
+
const lastCommitted = useRef<string | null>(null);
|
|
65
|
+
|
|
66
|
+
// A manual selection outranks the country default, so a later country change leaves it alone.
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (manuallyPicked.current) return;
|
|
69
|
+
setType(defaultTaxIdType(country));
|
|
70
|
+
}, [country]);
|
|
71
|
+
|
|
72
|
+
const options = useMemo(
|
|
73
|
+
() =>
|
|
74
|
+
TAX_ID_TYPES.map((candidate) => ({ value: candidate, label: labels[candidate] })).sort((a, b) =>
|
|
75
|
+
a.label.localeCompare(b.label),
|
|
76
|
+
),
|
|
77
|
+
[labels],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const latestChange = useRef(onChange);
|
|
81
|
+
latestChange.current = onChange;
|
|
82
|
+
|
|
83
|
+
const commit = useCallback(
|
|
84
|
+
(nextType: TaxIdType, nextValue: string) => {
|
|
85
|
+
const trimmed = nextValue.trim();
|
|
86
|
+
if (!trimmed) return;
|
|
87
|
+
const key = `${nextType}:${trimmed}`;
|
|
88
|
+
if (lastCommitted.current === key) return;
|
|
89
|
+
lastCommitted.current = key;
|
|
90
|
+
latestChange.current?.({ taxId: { type: nextType, value: trimmed } });
|
|
91
|
+
},
|
|
92
|
+
[],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const pickType = (next: string) => {
|
|
96
|
+
if (!isTaxIdType(next)) return;
|
|
97
|
+
manuallyPicked.current = true;
|
|
98
|
+
setType(next);
|
|
99
|
+
commit(next, value);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<ElementFrame
|
|
104
|
+
style={style}
|
|
105
|
+
fallback={fallback}
|
|
106
|
+
skeleton={
|
|
107
|
+
<Stack>
|
|
108
|
+
<FieldSkeleton />
|
|
109
|
+
<FieldSkeleton />
|
|
110
|
+
</Stack>
|
|
111
|
+
}
|
|
112
|
+
ready
|
|
113
|
+
onReady={onReady}
|
|
114
|
+
onError={onError}
|
|
115
|
+
>
|
|
116
|
+
<Stack>
|
|
117
|
+
<Select
|
|
118
|
+
label={t('Tax registration')}
|
|
119
|
+
value={type}
|
|
120
|
+
options={options}
|
|
121
|
+
onChange={pickType}
|
|
122
|
+
disabled={disabled}
|
|
123
|
+
searchable
|
|
124
|
+
partName="whop-Select"
|
|
125
|
+
/>
|
|
126
|
+
<Field
|
|
127
|
+
label={labels[type]}
|
|
128
|
+
value={value}
|
|
129
|
+
onChangeText={setValue}
|
|
130
|
+
onBlur={() => commit(type, value)}
|
|
131
|
+
onSubmitEditing={() => commit(type, value)}
|
|
132
|
+
placeholder={TAX_ID_TYPE_META[type].placeholder}
|
|
133
|
+
error={error}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
autoCapitalize="characters"
|
|
136
|
+
autoCorrect={false}
|
|
137
|
+
returnKeyType="done"
|
|
138
|
+
accessibilityLabel={labels[type]}
|
|
139
|
+
/>
|
|
140
|
+
</Stack>
|
|
141
|
+
</ElementFrame>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys are the English source strings, matching the web elements' `useExtracted()` keys exactly, so
|
|
3
|
+
* a translation written for one surface fits the other. English needs no catalog: a miss renders the
|
|
4
|
+
* key, which is already the English copy.
|
|
5
|
+
*/
|
|
6
|
+
export const CATALOGS: Record<string, Record<string, string> | undefined> = {
|
|
7
|
+
es: {
|
|
8
|
+
'Card number': 'Número de tarjeta',
|
|
9
|
+
Expiration: 'Vencimiento',
|
|
10
|
+
'Security code': 'Código de seguridad',
|
|
11
|
+
Email: 'Correo electrónico',
|
|
12
|
+
Country: 'País',
|
|
13
|
+
'Address line 1': 'Dirección línea 1',
|
|
14
|
+
'Address line 2': 'Dirección línea 2',
|
|
15
|
+
City: 'Ciudad',
|
|
16
|
+
'Postal code': 'Código postal',
|
|
17
|
+
State: 'Estado',
|
|
18
|
+
Province: 'Provincia',
|
|
19
|
+
Name: 'Nombre',
|
|
20
|
+
'First name': 'Nombre',
|
|
21
|
+
'Last name': 'Apellido',
|
|
22
|
+
Organization: 'Organización',
|
|
23
|
+
'Tax registration': 'Registro fiscal',
|
|
24
|
+
'Add address line 2': 'Agregar dirección línea 2',
|
|
25
|
+
'Powered by': 'Con tecnología de',
|
|
26
|
+
Terms: 'Términos',
|
|
27
|
+
Privacy: 'Privacidad',
|
|
28
|
+
'Payment method': 'Método de pago',
|
|
29
|
+
'No payment methods are available': 'No hay métodos de pago disponibles',
|
|
30
|
+
'Enter a valid email address': 'Ingresa un correo electrónico válido',
|
|
31
|
+
Required: 'Obligatorio',
|
|
32
|
+
"That doesn't look right": 'Eso no parece correcto',
|
|
33
|
+
Business: 'Empresa',
|
|
34
|
+
Individual: 'Particular',
|
|
35
|
+
Unavailable: 'No disponible',
|
|
36
|
+
'Save this payment method': 'Guardar este método de pago',
|
|
37
|
+
},
|
|
38
|
+
de: {
|
|
39
|
+
'Card number': 'Kartennummer',
|
|
40
|
+
Expiration: 'Ablaufdatum',
|
|
41
|
+
'Security code': 'Sicherheitscode',
|
|
42
|
+
Email: 'E-Mail',
|
|
43
|
+
Country: 'Land',
|
|
44
|
+
'Address line 1': 'Adresszeile 1',
|
|
45
|
+
'Address line 2': 'Adresszeile 2',
|
|
46
|
+
City: 'Stadt',
|
|
47
|
+
'Postal code': 'Postleitzahl',
|
|
48
|
+
State: 'Bundesland',
|
|
49
|
+
Province: 'Provinz',
|
|
50
|
+
Name: 'Name',
|
|
51
|
+
'First name': 'Vorname',
|
|
52
|
+
'Last name': 'Nachname',
|
|
53
|
+
Organization: 'Organisation',
|
|
54
|
+
'Tax registration': 'Steuernummer',
|
|
55
|
+
'Add address line 2': 'Adresszeile 2 hinzufügen',
|
|
56
|
+
'Powered by': 'Bereitgestellt von',
|
|
57
|
+
Terms: 'Bedingungen',
|
|
58
|
+
Privacy: 'Datenschutz',
|
|
59
|
+
'Payment method': 'Zahlungsmethode',
|
|
60
|
+
'No payment methods are available': 'Keine Zahlungsmethoden verfügbar',
|
|
61
|
+
'Enter a valid email address': 'Gib eine gültige E-Mail-Adresse ein',
|
|
62
|
+
Required: 'Erforderlich',
|
|
63
|
+
"That doesn't look right": 'Das sieht nicht richtig aus',
|
|
64
|
+
Business: 'Unternehmen',
|
|
65
|
+
Individual: 'Privatperson',
|
|
66
|
+
Unavailable: 'Nicht verfügbar',
|
|
67
|
+
'Save this payment method': 'Diese Zahlungsmethode speichern',
|
|
68
|
+
},
|
|
69
|
+
fr: {
|
|
70
|
+
'Card number': 'Numéro de carte',
|
|
71
|
+
Expiration: 'Expiration',
|
|
72
|
+
'Security code': 'Code de sécurité',
|
|
73
|
+
Email: 'E-mail',
|
|
74
|
+
Country: 'Pays',
|
|
75
|
+
'Address line 1': 'Adresse ligne 1',
|
|
76
|
+
'Address line 2': 'Adresse ligne 2',
|
|
77
|
+
City: 'Ville',
|
|
78
|
+
'Postal code': 'Code postal',
|
|
79
|
+
State: 'État',
|
|
80
|
+
Province: 'Province',
|
|
81
|
+
Name: 'Nom',
|
|
82
|
+
'First name': 'Prénom',
|
|
83
|
+
'Last name': 'Nom de famille',
|
|
84
|
+
Organization: 'Organisation',
|
|
85
|
+
'Tax registration': 'Numéro fiscal',
|
|
86
|
+
'Add address line 2': "Ajouter l'adresse ligne 2",
|
|
87
|
+
'Powered by': 'Propulsé par',
|
|
88
|
+
Terms: 'Conditions',
|
|
89
|
+
Privacy: 'Confidentialité',
|
|
90
|
+
'Payment method': 'Moyen de paiement',
|
|
91
|
+
'No payment methods are available': 'Aucun moyen de paiement disponible',
|
|
92
|
+
'Enter a valid email address': 'Saisis une adresse e-mail valide',
|
|
93
|
+
Required: 'Obligatoire',
|
|
94
|
+
"That doesn't look right": 'Cela ne semble pas correct',
|
|
95
|
+
Business: 'Entreprise',
|
|
96
|
+
Individual: 'Particulier',
|
|
97
|
+
Unavailable: 'Indisponible',
|
|
98
|
+
'Save this payment method': 'Enregistrer ce moyen de paiement',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext, createElement, type ReactNode, useContext, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { deviceLocale } from '../core/payments/browser-info';
|
|
4
|
+
import { CATALOGS } from './catalogs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Message lookup keyed by the English source string, the same shape `useExtracted()` gives the web
|
|
8
|
+
* elements — so a key here is the key there and translations move between them unchanged.
|
|
9
|
+
*
|
|
10
|
+
* A missing catalog entry renders the key, which IS the English copy. That is the correct failure:
|
|
11
|
+
* an untranslated string beats a blank one.
|
|
12
|
+
*/
|
|
13
|
+
export type Translate = (key: string, params?: Record<string, string | number>) => string;
|
|
14
|
+
|
|
15
|
+
const LocaleContext = createContext<string | undefined>(undefined);
|
|
16
|
+
|
|
17
|
+
export function LocaleProvider({ locale, children }: { locale?: string; children: ReactNode }): ReactNode {
|
|
18
|
+
return createElement(LocaleContext.Provider, { value: locale }, children);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useLocale(): string {
|
|
22
|
+
const explicit = useContext(LocaleContext);
|
|
23
|
+
return useMemo(() => normalize(explicit ?? deviceLocale()), [explicit]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useT(): Translate {
|
|
27
|
+
const locale = useLocale();
|
|
28
|
+
return useMemo(() => translator(locale), [locale]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function translator(locale: string): Translate {
|
|
32
|
+
const exact = CATALOGS[locale];
|
|
33
|
+
const base = CATALOGS[locale.split('-')[0] ?? ''];
|
|
34
|
+
return (key, params) => {
|
|
35
|
+
const template = exact?.[key] ?? base?.[key] ?? key;
|
|
36
|
+
if (!params) return template;
|
|
37
|
+
return template.replace(/\{(\w+)\}/g, (match, name: string) =>
|
|
38
|
+
Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match,
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalize(tag: string | undefined): string {
|
|
44
|
+
if (!tag) return 'en';
|
|
45
|
+
return tag.replace(/_/g, '-');
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@whop/elements-react-native` — native React Native components for collecting payments.
|
|
3
|
+
*
|
|
4
|
+
* The surface mirrors `@whop/elements-react`: a root provider, one namespace provider, one
|
|
5
|
+
* component per element, a hook for the live handle, and one `appearance` object. The deltas are
|
|
6
|
+
* documented on the types that carry them — `style` is a React Native style, `appearance.tokens`
|
|
7
|
+
* replaces CSS variables, `appearance.parts` replaces CSS classes, and there is no `elements`
|
|
8
|
+
* loader prop because there is no hosted script to load.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { WhopElements } from './provider';
|
|
12
|
+
export type { WhopElementsConfig, WhopElementsProps } from './provider';
|
|
13
|
+
|
|
14
|
+
export { Payments, usePayments } from './namespace';
|
|
15
|
+
export type { PaymentsHandle, PaymentsProviderProps } from './namespace';
|
|
16
|
+
export type { PaymentsOptions, PaymentsState } from './controller/store';
|
|
17
|
+
export type {
|
|
18
|
+
CreateConfirmationTokenInput,
|
|
19
|
+
CreateConfirmationTokenResult,
|
|
20
|
+
HandleNextActionInput,
|
|
21
|
+
HandleNextActionResult,
|
|
22
|
+
} from './controller/actions';
|
|
23
|
+
|
|
24
|
+
export { PaymentElement } from './elements/payment';
|
|
25
|
+
export type { PaymentChangePayload, PaymentElementProps } from './elements/payment';
|
|
26
|
+
|
|
27
|
+
export { AddressElement } from './elements/address';
|
|
28
|
+
export type { AddressChangePayload, AddressElementProps } from './elements/address';
|
|
29
|
+
|
|
30
|
+
export { CardElement } from './elements/card';
|
|
31
|
+
export type { CardElementProps } from './elements/card';
|
|
32
|
+
|
|
33
|
+
export { CardCvcElement, CardExpiryElement, CardFields, CardNumberElement } from './elements/card-fields';
|
|
34
|
+
export type { CardFieldProps, CardFieldsChangePayload, CardFieldsProps } from './elements/card-fields';
|
|
35
|
+
|
|
36
|
+
export { EmailElement } from './elements/email';
|
|
37
|
+
export type { EmailChangePayload, EmailElementProps } from './elements/email';
|
|
38
|
+
|
|
39
|
+
export { TaxIdElement } from './elements/tax-id';
|
|
40
|
+
export type { TaxIdChangePayload, TaxIdElementProps } from './elements/tax-id';
|
|
41
|
+
|
|
42
|
+
export { BrandingElement } from './elements/branding';
|
|
43
|
+
export type { BrandingElementProps } from './elements/branding';
|
|
44
|
+
|
|
45
|
+
export type {
|
|
46
|
+
AccentColor,
|
|
47
|
+
Appearance,
|
|
48
|
+
GrayColor,
|
|
49
|
+
Parts,
|
|
50
|
+
PartName,
|
|
51
|
+
Theme,
|
|
52
|
+
Tokens,
|
|
53
|
+
} from './theme/appearance';
|
|
54
|
+
export { useTheme } from './theme/provider';
|
|
55
|
+
export type { ResolvedTheme } from './theme/provider';
|
|
56
|
+
|
|
57
|
+
export { API_BASES, WhopError } from './api/client';
|
|
58
|
+
export type { WhopEnvironment } from './api/client';
|
|
59
|
+
|
|
60
|
+
export { BrandingRequiredError, ChargeConfigError, PaymentTokenizationError } from './core/payments/errors';
|
|
61
|
+
export { PaymentsRefusalError } from './core/payments/refusals';
|
|
62
|
+
export type { PaymentsRefusalCode } from './core/payments/refusals';
|
|
63
|
+
|
|
64
|
+
export type { TaxIdType } from './core/payments/tax-id-catalog';
|
|
65
|
+
export type { EmittedAddress } from './core/address/engine';
|
|
66
|
+
|
|
67
|
+
export { WhopAuthSession, WhopPlatformPay } from './native';
|
|
68
|
+
export type { WalletCapability } from './native';
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
forwardRef,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useImperativeHandle,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useSyncExternalStore,
|
|
12
|
+
} from 'react';
|
|
13
|
+
import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createConfirmationToken,
|
|
17
|
+
type CreateConfirmationTokenInput,
|
|
18
|
+
type CreateConfirmationTokenResult,
|
|
19
|
+
handleNextAction,
|
|
20
|
+
type HandleNextActionInput,
|
|
21
|
+
type HandleNextActionResult,
|
|
22
|
+
} from './controller/actions';
|
|
23
|
+
import { type PaymentsOptions, type PaymentsState, PaymentsStore } from './controller/store';
|
|
24
|
+
import { useRootConfig } from './provider';
|
|
25
|
+
|
|
26
|
+
/** The live handle a `<Payments>` ref exposes — the same surface the vanilla handle carries. */
|
|
27
|
+
export interface PaymentsHandle {
|
|
28
|
+
createConfirmationToken(input?: CreateConfirmationTokenInput): Promise<CreateConfirmationTokenResult>;
|
|
29
|
+
handleNextAction(input: HandleNextActionInput): Promise<HandleNextActionResult>;
|
|
30
|
+
getState(): PaymentsState;
|
|
31
|
+
subscribe(listener: () => void): () => void;
|
|
32
|
+
update(options: PaymentsOptions): void;
|
|
33
|
+
/** @internal the store the element components bind to */
|
|
34
|
+
readonly store: PaymentsStore;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PaymentsContext = createContext<PaymentsHandle | null | undefined>(undefined);
|
|
38
|
+
|
|
39
|
+
export function usePayments(): PaymentsHandle | null {
|
|
40
|
+
const handle = useContext(PaymentsContext);
|
|
41
|
+
if (handle === undefined) throw new Error('Payments element components must be used within <Payments>');
|
|
42
|
+
return handle;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @internal every element reads the store through this, never the context directly. */
|
|
46
|
+
export function usePaymentsStore(): PaymentsStore {
|
|
47
|
+
const handle = usePayments();
|
|
48
|
+
if (!handle) throw new Error('Payments element components must be used within <Payments>');
|
|
49
|
+
return handle.store;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @internal a selected slice of controller state, re-rendering only when the slice moves. */
|
|
53
|
+
export function usePaymentsState<T>(select: (state: PaymentsState) => T): T {
|
|
54
|
+
const store = usePaymentsStore();
|
|
55
|
+
const selector = useRef(select);
|
|
56
|
+
selector.current = select;
|
|
57
|
+
const getSnapshot = useCallback(() => selector.current(store.getSnapshot()), [store]);
|
|
58
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PaymentsProviderProps extends PaymentsOptions {
|
|
62
|
+
children?: ReactNode;
|
|
63
|
+
/** Shown until every child element has loaded. Grouped, exactly like the web provider's. */
|
|
64
|
+
fallback?: ReactNode;
|
|
65
|
+
style?: StyleProp<ViewStyle>;
|
|
66
|
+
onError?: (error: { message: string; code?: string }) => void;
|
|
67
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const Payments = forwardRef<PaymentsHandle | null, PaymentsProviderProps>(function Payments(props, ref) {
|
|
71
|
+
const { children, fallback, style, onError, onLoadingChange, ...options } = props;
|
|
72
|
+
const config = useRootConfig();
|
|
73
|
+
|
|
74
|
+
// Read through a ref so the store never depends on getToken's IDENTITY. Every published sample
|
|
75
|
+
// passes an inline `getToken={() => ...}`, which is a new function each render: keying the store
|
|
76
|
+
// on it destroyed the store on any parent re-render, remounting card fields and dropping
|
|
77
|
+
// in-flight confirms.
|
|
78
|
+
const latestGetToken = useRef(config.getToken);
|
|
79
|
+
latestGetToken.current = config.getToken;
|
|
80
|
+
|
|
81
|
+
const store = useMemo(
|
|
82
|
+
() =>
|
|
83
|
+
new PaymentsStore(options, config.environment ?? 'production', async () => {
|
|
84
|
+
const token = await latestGetToken.current?.();
|
|
85
|
+
return token ?? null;
|
|
86
|
+
}),
|
|
87
|
+
// The store owns its own option updates below; recreating it on every prop change would
|
|
88
|
+
// remount every field and reload the tokenizer.
|
|
89
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
|
+
[config.environment],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
useEffect(() => () => store.destroy(), [store]);
|
|
94
|
+
|
|
95
|
+
const latestOptions = useRef(options);
|
|
96
|
+
latestOptions.current = options;
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
store.update(latestOptions.current);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const chargeError = usePaymentsStateOf(store, (s) => s.chargeError);
|
|
102
|
+
const latestOnError = useRef(onError);
|
|
103
|
+
latestOnError.current = onError;
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!chargeError) return;
|
|
106
|
+
const code = (chargeError as { code?: string }).code;
|
|
107
|
+
latestOnError.current?.({ message: chargeError.message, code });
|
|
108
|
+
}, [chargeError]);
|
|
109
|
+
|
|
110
|
+
const handle = useMemo<PaymentsHandle>(
|
|
111
|
+
() => ({
|
|
112
|
+
store,
|
|
113
|
+
createConfirmationToken: (input) => createConfirmationToken(store, input),
|
|
114
|
+
handleNextAction: (input) => handleNextAction(store, input),
|
|
115
|
+
getState: () => store.getSnapshot(),
|
|
116
|
+
subscribe: (listener) => store.subscribe(listener),
|
|
117
|
+
update: (next) => store.update(next),
|
|
118
|
+
}),
|
|
119
|
+
[store],
|
|
120
|
+
);
|
|
121
|
+
useImperativeHandle(ref, () => handle, [handle]);
|
|
122
|
+
|
|
123
|
+
const loading = useGroupLoading(store, onLoadingChange);
|
|
124
|
+
const hideChildren = loading && fallback != null;
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<PaymentsContext.Provider value={handle}>
|
|
128
|
+
<View style={style}>
|
|
129
|
+
{hideChildren ? fallback : null}
|
|
130
|
+
{/*
|
|
131
|
+
Held children stay mounted, laid out, and merely invisible. `display: none` would
|
|
132
|
+
collapse them to zero, and the Basis Theory fields measure themselves on mount — a
|
|
133
|
+
collapsed field initializes at 0x0 and pops when revealed.
|
|
134
|
+
*/}
|
|
135
|
+
<View
|
|
136
|
+
style={hideChildren ? styles.held : undefined}
|
|
137
|
+
pointerEvents={hideChildren ? 'none' : 'auto'}
|
|
138
|
+
accessibilityElementsHidden={hideChildren}
|
|
139
|
+
importantForAccessibility={hideChildren ? 'no-hide-descendants' : 'auto'}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</View>
|
|
143
|
+
</View>
|
|
144
|
+
</PaymentsContext.Provider>
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/** @internal element components call this on mount so the group latch knows to wait for them. */
|
|
149
|
+
export function useGroupMember(): { ready: () => void } {
|
|
150
|
+
const store = usePaymentsStore();
|
|
151
|
+
const settled = useRef(false);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const registry = groupRegistry(store);
|
|
154
|
+
registry.pending += 1;
|
|
155
|
+
registry.notify();
|
|
156
|
+
return () => {
|
|
157
|
+
if (settled.current) return;
|
|
158
|
+
registry.pending -= 1;
|
|
159
|
+
registry.notify();
|
|
160
|
+
};
|
|
161
|
+
}, [store]);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
ready: () => {
|
|
165
|
+
if (settled.current) return;
|
|
166
|
+
settled.current = true;
|
|
167
|
+
const registry = groupRegistry(store);
|
|
168
|
+
registry.pending -= 1;
|
|
169
|
+
registry.notify();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface GroupRegistry {
|
|
175
|
+
pending: number;
|
|
176
|
+
listeners: Set<() => void>;
|
|
177
|
+
notify: () => void;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const REGISTRIES = new WeakMap<PaymentsStore, GroupRegistry>();
|
|
181
|
+
|
|
182
|
+
function groupRegistry(store: PaymentsStore): GroupRegistry {
|
|
183
|
+
let registry = REGISTRIES.get(store);
|
|
184
|
+
if (!registry) {
|
|
185
|
+
const created: GroupRegistry = {
|
|
186
|
+
pending: 0,
|
|
187
|
+
listeners: new Set(),
|
|
188
|
+
notify: () => {
|
|
189
|
+
for (const listener of created.listeners) listener();
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
REGISTRIES.set(store, created);
|
|
193
|
+
registry = created;
|
|
194
|
+
}
|
|
195
|
+
return registry;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function useGroupLoading(store: PaymentsStore, onLoadingChange?: (loading: boolean) => void): boolean {
|
|
199
|
+
const subscribe = useCallback(
|
|
200
|
+
(listener: () => void) => {
|
|
201
|
+
const registry = groupRegistry(store);
|
|
202
|
+
registry.listeners.add(listener);
|
|
203
|
+
return () => {
|
|
204
|
+
registry.listeners.delete(listener);
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
[store],
|
|
208
|
+
);
|
|
209
|
+
// Nothing mounted eagerly means nothing will ever settle the latch, so an empty group is loaded.
|
|
210
|
+
const getSnapshot = useCallback(() => groupRegistry(store).pending > 0, [store]);
|
|
211
|
+
const loading = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
212
|
+
|
|
213
|
+
const latest = useRef(onLoadingChange);
|
|
214
|
+
latest.current = onLoadingChange;
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
latest.current?.(loading);
|
|
217
|
+
}, [loading]);
|
|
218
|
+
|
|
219
|
+
return loading;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function usePaymentsStateOf<T>(store: PaymentsStore, select: (state: PaymentsState) => T): T {
|
|
223
|
+
const selector = useRef(select);
|
|
224
|
+
selector.current = select;
|
|
225
|
+
const getSnapshot = useCallback(() => selector.current(store.getSnapshot()), [store]);
|
|
226
|
+
return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const styles = StyleSheet.create({
|
|
230
|
+
held: { position: 'absolute', top: 0, left: 0, right: 0, opacity: 0 },
|
|
231
|
+
});
|