@momo-kits/foundation 0.168.1-beta.9 → 0.168.1-duo.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/Input/InputMoney.tsx +15 -6
- package/Input/styles.ts +2 -1
- package/Layout/Screen.tsx +33 -4
- package/Layout/styles.ts +6 -0
- package/package.json +1 -1
package/Input/InputMoney.tsx
CHANGED
|
@@ -37,6 +37,8 @@ import SystemTextInput from './SystemTextInput';
|
|
|
37
37
|
|
|
38
38
|
const moneyFormatter = new Intl.NumberFormat('vi-VN');
|
|
39
39
|
|
|
40
|
+
// Finds the common prefix and suffix of `old` and `text`; `size` counts the
|
|
41
|
+
// characters removed plus inserted between them.
|
|
40
42
|
const findEdit = (old: string, text: string) => {
|
|
41
43
|
let start = 0;
|
|
42
44
|
while (start < text.length && old[start] === text[start]) {
|
|
@@ -127,8 +129,11 @@ const InputMoney = forwardRef(
|
|
|
127
129
|
setLastDisplayValue(displayValue);
|
|
128
130
|
}, [displayValue]);
|
|
129
131
|
|
|
132
|
+
// The selection only rides along with the render that reformats the text,
|
|
133
|
+
// then it is released so the user can move the caret freely.
|
|
130
134
|
useEffect(() => {
|
|
131
135
|
if (!caretSelection) return;
|
|
136
|
+
// A newer keystroke may already have queued its own selection; keep it.
|
|
132
137
|
setCaretSelection(current =>
|
|
133
138
|
current === caretSelection ? undefined : current,
|
|
134
139
|
);
|
|
@@ -138,6 +143,9 @@ const InputMoney = forwardRef(
|
|
|
138
143
|
const next = numeric ? moneyFormatter.format(Number(numeric)) : '';
|
|
139
144
|
setDisplayValue(next);
|
|
140
145
|
|
|
146
|
+
// Android moves the caret proportionally when the text is replaced, so
|
|
147
|
+
// it lands before the typed digit once a separator is added. Send the
|
|
148
|
+
// caret together with the text, keeping the same digits after it.
|
|
141
149
|
const digitsAfter = caretDigitsAfter.current;
|
|
142
150
|
caretDigitsAfter.current = undefined;
|
|
143
151
|
if (Platform.OS !== 'android' || digitsAfter === undefined) return;
|
|
@@ -179,6 +187,9 @@ const InputMoney = forwardRef(
|
|
|
179
187
|
return;
|
|
180
188
|
}
|
|
181
189
|
const digits = parseInputText(text);
|
|
190
|
+
// When typing outpaces JS (slow devices) native rejects the stale
|
|
191
|
+
// formatted text and keeps the raw one, so the edit may apply to either.
|
|
192
|
+
// Pick the one that differs from the new text by a single edit.
|
|
182
193
|
const raw = lastRawText.current;
|
|
183
194
|
lastRawText.current = text;
|
|
184
195
|
const formattedEdit = findEdit(lastDisplayValue, text);
|
|
@@ -188,6 +199,7 @@ const InputMoney = forwardRef(
|
|
|
188
199
|
caretDigitsAfter.current = parseInputText(
|
|
189
200
|
text.slice(text.length - suffix),
|
|
190
201
|
).length;
|
|
202
|
+
// Deleting a separator removes the digit before it.
|
|
191
203
|
if (text.length < old.length && digits === numericValue) {
|
|
192
204
|
const index = parseInputText(text.slice(0, start)).length - 1;
|
|
193
205
|
if (index >= 0) {
|
|
@@ -198,11 +210,6 @@ const InputMoney = forwardRef(
|
|
|
198
210
|
updateNumericValue(digits);
|
|
199
211
|
};
|
|
200
212
|
|
|
201
|
-
const focusAtEnd = () => {
|
|
202
|
-
inputRef.current?.focus();
|
|
203
|
-
inputRef.current?.setSelection(displayValue.length, displayValue.length);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
213
|
const _onFocus = (e: TextInputFocusEvent) => {
|
|
207
214
|
setFocused(true);
|
|
208
215
|
onFocus?.(e);
|
|
@@ -272,7 +279,7 @@ const InputMoney = forwardRef(
|
|
|
272
279
|
/>
|
|
273
280
|
<Pressable
|
|
274
281
|
style={[styles.inputView, styles.moneyInputView]}
|
|
275
|
-
onPress={
|
|
282
|
+
onPress={() => inputRef.current?.focus()}
|
|
276
283
|
disabled={disabled}
|
|
277
284
|
>
|
|
278
285
|
<SystemTextInput
|
|
@@ -284,6 +291,8 @@ const InputMoney = forwardRef(
|
|
|
284
291
|
disabled: disabled,
|
|
285
292
|
...props.accessibilityState,
|
|
286
293
|
}}
|
|
294
|
+
// No width: the input sizes to its text natively, so the
|
|
295
|
+
// currency follows it without waiting for JS.
|
|
287
296
|
style={[textStyle, styles.moneyTextInput, { color: textColor }]}
|
|
288
297
|
allowFontScaling={false}
|
|
289
298
|
textBreakStrategy="highQuality"
|
package/Input/styles.ts
CHANGED
package/Layout/Screen.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import React, {
|
|
|
11
11
|
useImperativeHandle,
|
|
12
12
|
useMemo,
|
|
13
13
|
useRef,
|
|
14
|
+
useState,
|
|
14
15
|
} from 'react';
|
|
15
16
|
import {
|
|
16
17
|
Animated,
|
|
@@ -25,8 +26,9 @@ import {
|
|
|
25
26
|
View,
|
|
26
27
|
ViewProps,
|
|
27
28
|
} from 'react-native';
|
|
28
|
-
import {
|
|
29
|
+
import ReAnimated, {
|
|
29
30
|
useAnimatedReaction,
|
|
31
|
+
useAnimatedStyle,
|
|
30
32
|
useSharedValue,
|
|
31
33
|
withTiming,
|
|
32
34
|
} from 'react-native-reanimated';
|
|
@@ -280,6 +282,14 @@ const Screen = forwardRef(
|
|
|
280
282
|
const headerHide = useSharedValue(0);
|
|
281
283
|
const lastScrollY = useSharedValue(0);
|
|
282
284
|
|
|
285
|
+
// iPhone Duo: the header component rides under the quick return header and the
|
|
286
|
+
// content scrolls beneath both, instead of staying pinned below an empty band
|
|
287
|
+
const floatHeaderComponent = useDuoGlass && scrollable && !!Header;
|
|
288
|
+
const [headerComponentHeight, setHeaderComponentHeight] = useState(0);
|
|
289
|
+
const headerComponentStyle = useAnimatedStyle(() => ({
|
|
290
|
+
transform: [{ translateY: -headerHide.value }],
|
|
291
|
+
}));
|
|
292
|
+
|
|
283
293
|
useAnimatedReaction(
|
|
284
294
|
() => sharedValue.value,
|
|
285
295
|
y => {
|
|
@@ -780,7 +790,7 @@ const Screen = forwardRef(
|
|
|
780
790
|
paddingLeft: layout.insetLeft,
|
|
781
791
|
paddingRight: layout.insetRight,
|
|
782
792
|
};
|
|
783
|
-
const offsetBodyByHeader = useDuoGlass &&
|
|
793
|
+
const offsetBodyByHeader = useDuoGlass && !scrollable;
|
|
784
794
|
const hasDuoTabInset = !Footer && isTab && !!duoTabInset;
|
|
785
795
|
|
|
786
796
|
let floatingButtonBottom = bottomInset + Spacing.S;
|
|
@@ -838,7 +848,22 @@ const Screen = forwardRef(
|
|
|
838
848
|
android: undefined,
|
|
839
849
|
})}
|
|
840
850
|
>
|
|
841
|
-
{
|
|
851
|
+
{floatHeaderComponent ? (
|
|
852
|
+
<ReAnimated.View
|
|
853
|
+
style={[
|
|
854
|
+
styles.floatingHeaderComponent,
|
|
855
|
+
{ top: heightHeader },
|
|
856
|
+
headerComponentStyle,
|
|
857
|
+
]}
|
|
858
|
+
onLayout={e =>
|
|
859
|
+
setHeaderComponentHeight(e.nativeEvent.layout.height)
|
|
860
|
+
}
|
|
861
|
+
>
|
|
862
|
+
{Header}
|
|
863
|
+
</ReAnimated.View>
|
|
864
|
+
) : (
|
|
865
|
+
Header
|
|
866
|
+
)}
|
|
842
867
|
|
|
843
868
|
<Component
|
|
844
869
|
{...scrollViewProps}
|
|
@@ -857,7 +882,11 @@ const Screen = forwardRef(
|
|
|
857
882
|
scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
|
|
858
883
|
scrollable && hasDuoTabInset && { paddingBottom: duoTabInset },
|
|
859
884
|
scrollable && contentInsetStyle,
|
|
860
|
-
useDuoGlass &&
|
|
885
|
+
useDuoGlass && {
|
|
886
|
+
paddingTop:
|
|
887
|
+
heightHeader +
|
|
888
|
+
(floatHeaderComponent ? headerComponentHeight : 0),
|
|
889
|
+
},
|
|
861
890
|
scrollViewProps?.contentContainerStyle,
|
|
862
891
|
]}
|
|
863
892
|
>
|
package/Layout/styles.ts
CHANGED