@namiml/expo-sdk 3.4.7-dev.202607230540 → 3.4.7-dev.202607231500
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namiml/expo-sdk",
|
|
3
|
-
"version": "3.4.7-dev.
|
|
3
|
+
"version": "3.4.7-dev.202607231500",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nami Expo SDK — paywall and subscription management for Expo apps",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"prepublishOnly": "yarn clean && yarn build:prod"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@namiml/expo-nami-iap": "3.4.7-dev.
|
|
33
|
-
"@namiml/sdk-core": "3.4.7-dev.
|
|
32
|
+
"@namiml/expo-nami-iap": "3.4.7-dev.202607231500",
|
|
33
|
+
"@namiml/sdk-core": "3.4.7-dev.202607231500",
|
|
34
34
|
"react-native-qrcode-svg": "^6.3.21",
|
|
35
35
|
"react-native-safe-area-context": "^5.6.0",
|
|
36
36
|
"react-native-svg": "^15.15.4"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { TextInput, View, Text } from 'react-native';
|
|
3
3
|
import type { ViewStyle } from 'react-native';
|
|
4
4
|
import {
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
getDeviceFormFactor,
|
|
7
7
|
validateTextInput,
|
|
8
8
|
} from '@namiml/sdk-core';
|
|
9
|
-
import { usePaywallContext } from '../../context/PaywallContext';
|
|
9
|
+
import { useFirstFocusReadyContext, usePaywallContext } from '../../context/PaywallContext';
|
|
10
|
+
import { useFocusEnabled, useRegisterPreferredFocus } from '../../context/FocusContext';
|
|
11
|
+
import { useTVPreferredFocus } from '../../utils/tvFocus';
|
|
10
12
|
import {
|
|
11
13
|
paddingAndMarginStyles,
|
|
12
14
|
parseColor,
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
} from '../../utils/styles';
|
|
19
21
|
import {
|
|
20
22
|
TV_DEFAULTS,
|
|
23
|
+
resolveAutoFocus,
|
|
21
24
|
resolveInputVisual,
|
|
22
25
|
resolveLabelText,
|
|
23
26
|
resolveSpacing,
|
|
@@ -45,11 +48,23 @@ interface Props {
|
|
|
45
48
|
* Styling follows the 4-state precedence (error > focused > filled > empty) and TV
|
|
46
49
|
* defaults, resolved in the pure `./textInputStyle` helpers. Form-factor detection
|
|
47
50
|
* goes through core's `getDeviceFormFactor()` (honors `Nami.configure({ formFactor })`).
|
|
51
|
+
*
|
|
52
|
+
* `component.focused` (NAM-1529) drives autofocus on mount, mirroring the
|
|
53
|
+
* `NamiButton` `focused` precedent: `useTVPreferredFocus` (utils/tvFocus.ts) calls
|
|
54
|
+
* `.focus()` on mobile (raises the keyboard) but is a no-op on TV, where
|
|
55
|
+
* `hasTVPreferredFocus` sets d-pad focus only. The `focused` useState below is
|
|
56
|
+
* unrelated — it only drives the 4-state visual precedence.
|
|
48
57
|
*/
|
|
49
58
|
export const NamiTextInput: React.FC<Props> = ({ component, scaleFactor, parentDirection }) => {
|
|
50
59
|
const ctx = usePaywallContext();
|
|
60
|
+
const focusReadyCtx = useFirstFocusReadyContext();
|
|
61
|
+
const focusEnabled = useFocusEnabled();
|
|
51
62
|
const formId = component.formId;
|
|
52
63
|
const [focused, setFocused] = useState(false);
|
|
64
|
+
const inputRef = useRef<TextInput>(null);
|
|
65
|
+
const autoFocus = resolveAutoFocus(component, focusEnabled);
|
|
66
|
+
useTVPreferredFocus(inputRef, autoFocus);
|
|
67
|
+
useRegisterPreferredFocus(inputRef.current, autoFocus);
|
|
53
68
|
|
|
54
69
|
// Seed form state + register the validator once per field.
|
|
55
70
|
useEffect(() => {
|
|
@@ -76,6 +91,14 @@ export const NamiTextInput: React.FC<Props> = ({ component, scaleFactor, parentD
|
|
|
76
91
|
}
|
|
77
92
|
}, [formId, ctx, component]);
|
|
78
93
|
|
|
94
|
+
const handleFocus = useCallback(() => {
|
|
95
|
+
setFocused(true);
|
|
96
|
+
const paywallId = ctx.state.selectedPaywall?.id ?? 'unknown';
|
|
97
|
+
const page = ctx.state.currentPage ?? 'unknown';
|
|
98
|
+
const formFactor = ctx.state.formFactor;
|
|
99
|
+
focusReadyCtx.notifyFirstFocusReady(paywallId, page, formFactor);
|
|
100
|
+
}, [focusReadyCtx, ctx.state.selectedPaywall?.id, ctx.state.currentPage, ctx.state.formFactor]);
|
|
101
|
+
|
|
79
102
|
const isTelevision = getDeviceFormFactor() === 'television';
|
|
80
103
|
const visual = resolveInputVisual(component, { focused, hasError: !!error, isTelevision });
|
|
81
104
|
const spacing = resolveSpacing(component, isTelevision);
|
|
@@ -136,9 +159,10 @@ export const NamiTextInput: React.FC<Props> = ({ component, scaleFactor, parentD
|
|
|
136
159
|
<View style={wrapperStyle}>
|
|
137
160
|
{showLabel ? <Text style={labelStyle}>{labelText}</Text> : null}
|
|
138
161
|
<TextInput
|
|
162
|
+
ref={inputRef}
|
|
139
163
|
value={value}
|
|
140
164
|
onChangeText={onChangeText}
|
|
141
|
-
onFocus={
|
|
165
|
+
onFocus={handleFocus}
|
|
142
166
|
onBlur={() => setFocused(false)}
|
|
143
167
|
placeholder={component.placeholderText}
|
|
144
168
|
placeholderTextColor={parseColor(component.placeholderFontColor)}
|
|
@@ -146,6 +170,7 @@ export const NamiTextInput: React.FC<Props> = ({ component, scaleFactor, parentD
|
|
|
146
170
|
autoCapitalize={isEmail ? 'none' : 'sentences'}
|
|
147
171
|
autoCorrect={!isEmail}
|
|
148
172
|
style={inputStyle}
|
|
173
|
+
hasTVPreferredFocus={autoFocus}
|
|
149
174
|
accessibilityLabel={component.screenreaderText ?? labelText}
|
|
150
175
|
accessibilityHint={component.screenreaderHint}
|
|
151
176
|
/>
|
|
@@ -124,3 +124,18 @@ export function resolveSpacing(component: TTextInputComponent, isTelevision: boo
|
|
|
124
124
|
if (spacing == null) return isTelevision ? TV_DEFAULTS.spacing : 0;
|
|
125
125
|
return Number(spacing);
|
|
126
126
|
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Whether this field should claim autofocus on mount (NAM-1529), mirroring the
|
|
130
|
+
* button `focused` precedent (`NamiButton.tsx`): the payload's `focused` flag
|
|
131
|
+
* only applies while the surrounding focus scope (`useFocusEnabled`) allows it,
|
|
132
|
+
* so an off-screen/inactive step never steals focus. The mobile-vs-TV split
|
|
133
|
+
* (keyboard raise vs. d-pad-only) lives in `useTVPreferredFocus`
|
|
134
|
+
* (utils/tvFocus.ts) — this only decides whether autofocus applies at all.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveAutoFocus(
|
|
137
|
+
component: Pick<TTextInputComponent, 'focused'>,
|
|
138
|
+
focusEnabled: boolean,
|
|
139
|
+
): boolean {
|
|
140
|
+
return focusEnabled && Boolean(component.focused);
|
|
141
|
+
}
|