react-native-transformer-text-input 0.1.0-alpha.5 → 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 +17 -1
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorView.kt +30 -0
- package/cpp/TransformerTextInputDecoratorViewShadowNode.cpp +24 -1
- package/ios/TransformerTextInputDecoratorView.mm +29 -1
- package/lib/module/TransformerTextInput.js +24 -0
- package/lib/module/TransformerTextInput.js.map +1 -1
- package/lib/module/formatters/currency.js +122 -0
- package/lib/module/formatters/currency.js.map +1 -0
- package/lib/module/registry.js +4 -3
- package/lib/module/registry.js.map +1 -1
- package/lib/typescript/src/TransformerTextInput.d.ts.map +1 -1
- package/lib/typescript/src/formatters/currency.d.ts +17 -0
- package/lib/typescript/src/formatters/currency.d.ts.map +1 -0
- package/lib/typescript/src/registry.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/TransformerTextInput.tsx +23 -1
- package/src/formatters/currency.ts +142 -0
- package/src/registry.ts +4 -3
package/README.md
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
<img width="100%" height="autp" alt="react-native-transformer-input by App&Flow" src="https://github.com/user-attachments/assets/994aa73e-2db6-434c-bdd5-3069658e7c2c" />
|
|
2
|
+
|
|
2
3
|
|
|
3
4
|
TextInput component that allows transforming text synchronously with a worklet.
|
|
4
5
|
|
|
6
|
+
|
|
7
|
+
## About
|
|
8
|
+
App & Flow is a Montreal-based React Native engineering and consulting studio. We partner with the world’s top companies and are recommended by [Expo](https://expo.dev/consultants). Need a hand? Let’s build together. team@appandflow.com
|
|
9
|
+
|
|
10
|
+
## Demo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
https://github.com/user-attachments/assets/b22041b2-f5c1-4b2a-9fd0-960d7f5b3cf9
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## Motivation
|
|
6
22
|
|
|
7
23
|
Transforming input as users type is common — phone numbers, credit cards, usernames. Existing approaches have trade-offs:
|
|
@@ -51,8 +51,38 @@ class TransformerTextInputDecoratorView(
|
|
|
51
51
|
) ?: state
|
|
52
52
|
|
|
53
53
|
fun setTransformerId(newTransformerId: Int) {
|
|
54
|
+
val previousTransformerId = transformerId
|
|
54
55
|
transformerId = newTransformerId
|
|
55
56
|
lastEventValue = null
|
|
57
|
+
// When the transformer is swapped after mount, re-run it on the current
|
|
58
|
+
// text so the displayed value reformats immediately. The initial prop
|
|
59
|
+
// set is excluded by checking that the previous id was non-zero
|
|
60
|
+
// (default) and that the backing edit text is attached.
|
|
61
|
+
if (previousTransformerId != 0 &&
|
|
62
|
+
previousTransformerId != newTransformerId &&
|
|
63
|
+
reactEditText != null
|
|
64
|
+
) {
|
|
65
|
+
reapplyTransformer()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun reapplyTransformer() {
|
|
70
|
+
val currentValue = currentValue()
|
|
71
|
+
val currentSelection = currentSelection()
|
|
72
|
+
val current = TextState(currentValue, currentSelection)
|
|
73
|
+
val next = transformTextState(current, true)
|
|
74
|
+
if (next.value == currentValue && next.selection == currentSelection) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
isUpdating = true
|
|
78
|
+
try {
|
|
79
|
+
if (next.value != currentValue) {
|
|
80
|
+
applyValue(next.value)
|
|
81
|
+
}
|
|
82
|
+
applySelection(next.selection)
|
|
83
|
+
} finally {
|
|
84
|
+
isUpdating = false
|
|
85
|
+
}
|
|
56
86
|
}
|
|
57
87
|
|
|
58
88
|
override fun onAttachedToWindow() {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "TransformerTextInputDecoratorViewShadowNode.h"
|
|
2
2
|
|
|
3
|
+
#include <react/debug/react_native_assert.h>
|
|
4
|
+
|
|
3
5
|
namespace facebook::react {
|
|
4
6
|
|
|
5
7
|
const char TransformerTextInputDecoratorViewComponentName[] =
|
|
@@ -15,7 +17,28 @@ void TransformerTextInputDecoratorViewShadowNode::initialize() {
|
|
|
15
17
|
|
|
16
18
|
void TransformerTextInputDecoratorViewShadowNode::layout(
|
|
17
19
|
LayoutContext layoutContext) {
|
|
18
|
-
|
|
20
|
+
YogaLayoutableShadowNode::layout(layoutContext);
|
|
21
|
+
|
|
22
|
+
// Nodes with display: contents are skipped during Yoga layout and end up
|
|
23
|
+
// with zero-sized metrics. To get a proper host view (so input and
|
|
24
|
+
// accessibility wiring works on Android), copy the child's Yoga-computed
|
|
25
|
+
// metrics onto the decorator and zero out the child's origin since it is
|
|
26
|
+
// now relative to the decorator.
|
|
27
|
+
const auto &children = getChildren();
|
|
28
|
+
react_native_assert(
|
|
29
|
+
children.size() == 1 &&
|
|
30
|
+
"TransformerTextInputDecoratorView didn't receive exactly one child");
|
|
31
|
+
|
|
32
|
+
auto child =
|
|
33
|
+
std::static_pointer_cast<const YogaLayoutableShadowNode>(children[0]);
|
|
34
|
+
child->ensureUnsealed();
|
|
35
|
+
auto mutableChild = std::const_pointer_cast<YogaLayoutableShadowNode>(child);
|
|
36
|
+
|
|
37
|
+
auto childMetrics = child->getLayoutMetrics();
|
|
38
|
+
setLayoutMetrics(childMetrics);
|
|
39
|
+
|
|
40
|
+
childMetrics.frame.origin = Point{};
|
|
41
|
+
mutableChild->setLayoutMetrics(childMetrics);
|
|
19
42
|
}
|
|
20
43
|
|
|
21
44
|
} // namespace facebook::react
|
|
@@ -50,11 +50,39 @@ struct RNTTITextState {
|
|
|
50
50
|
const auto &oldViewProps = *std::static_pointer_cast<TransformerTextInputDecoratorViewProps const>(_props);
|
|
51
51
|
const auto &newViewProps = *std::static_pointer_cast<TransformerTextInputDecoratorViewProps const>(props);
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
bool transformerIdChanged = oldViewProps.transformerId != newViewProps.transformerId;
|
|
54
|
+
if (transformerIdChanged) {
|
|
54
55
|
_transformer = rntti::LookupTransformer(newViewProps.transformerId);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
[super updateProps:props oldProps:oldProps];
|
|
59
|
+
|
|
60
|
+
// When the transformer is swapped after mount, re-run it on the current
|
|
61
|
+
// text so the displayed value reformats immediately. The initial prop set
|
|
62
|
+
// is excluded by checking that the previous id was non-zero (default) and
|
|
63
|
+
// that the backing text input is attached.
|
|
64
|
+
if (transformerIdChanged && oldViewProps.transformerId != 0 && _backedTextInput != nil) {
|
|
65
|
+
[self reapplyTransformer];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (void)reapplyTransformer
|
|
70
|
+
{
|
|
71
|
+
NSString *currentValue = [self currentValue];
|
|
72
|
+
NSRange currentSelection = [self currentSelection];
|
|
73
|
+
RNTTITextState current{currentValue, currentSelection};
|
|
74
|
+
RNTTITextState next = [self transformTextState:current transform:YES];
|
|
75
|
+
bool didTransformValue = ![next.value isEqualToString:currentValue];
|
|
76
|
+
if (!didTransformValue && NSEqualRanges(next.selection, currentSelection)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (didTransformValue) {
|
|
80
|
+
[self applyValue:next.value];
|
|
81
|
+
}
|
|
82
|
+
[self applySelection:next.selection];
|
|
83
|
+
if (didTransformValue) {
|
|
84
|
+
[_baseDelegate textInputDidChange];
|
|
85
|
+
}
|
|
58
86
|
}
|
|
59
87
|
|
|
60
88
|
- (void)applyValue:(NSString *)newValue
|
|
@@ -10,11 +10,34 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
10
10
|
export const TransformerTextInput = /*#__PURE__*/forwardRef(({
|
|
11
11
|
transformer,
|
|
12
12
|
onChangeText,
|
|
13
|
+
defaultValue,
|
|
13
14
|
...others
|
|
14
15
|
}, forwardedRef) => {
|
|
15
16
|
const transformerId = useMemo(() => {
|
|
16
17
|
return registerTransformer(transformer);
|
|
17
18
|
}, [transformer]);
|
|
19
|
+
|
|
20
|
+
// Pre-transform defaultValue on the JS thread so Yoga measures the correct
|
|
21
|
+
// text from the start. Without this the native-side transformation happens
|
|
22
|
+
// after layout and doesn't trigger a remeasure.
|
|
23
|
+
const transformedDefaultValue = useMemo(() => {
|
|
24
|
+
if (defaultValue == null) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const result = transformer.worklet({
|
|
28
|
+
value: defaultValue,
|
|
29
|
+
previousValue: defaultValue,
|
|
30
|
+
selection: {
|
|
31
|
+
start: defaultValue.length,
|
|
32
|
+
end: defaultValue.length
|
|
33
|
+
},
|
|
34
|
+
previousSelection: {
|
|
35
|
+
start: 0,
|
|
36
|
+
end: 0
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return result?.value ?? defaultValue;
|
|
40
|
+
}, [defaultValue, transformer]);
|
|
18
41
|
useEffect(() => {
|
|
19
42
|
return () => {
|
|
20
43
|
unregisterTransformer(transformerId);
|
|
@@ -74,6 +97,7 @@ export const TransformerTextInput = /*#__PURE__*/forwardRef(({
|
|
|
74
97
|
, {
|
|
75
98
|
ref: inputRef,
|
|
76
99
|
onChangeText: handleChangeText,
|
|
100
|
+
defaultValue: transformedDefaultValue,
|
|
77
101
|
...others
|
|
78
102
|
})
|
|
79
103
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["forwardRef","useCallback","useEffect","useMemo","useRef","StyleSheet","TextInput","TransformerTextInputDecoratorViewNativeComponent","Commands","registerTransformer","unregisterTransformer","useMergeRefs","validateSelection","jsx","_jsx","TransformerTextInput","transformer","onChangeText","others","forwardedRef","transformerId","
|
|
1
|
+
{"version":3,"names":["forwardRef","useCallback","useEffect","useMemo","useRef","StyleSheet","TextInput","TransformerTextInputDecoratorViewNativeComponent","Commands","registerTransformer","unregisterTransformer","useMergeRefs","validateSelection","jsx","_jsx","TransformerTextInput","transformer","onChangeText","defaultValue","others","forwardedRef","transformerId","transformedDefaultValue","undefined","result","worklet","value","previousValue","selection","start","length","end","previousSelection","decoratorRef","textRef","setInputRef","instance","Object","assign","getValue","current","update","transform","nativeRef","newSelection","newValue","clear","inputRef","handleChangeText","text","ref","style","styles","decorator","children","create","display"],"sourceRoot":"../../src","sources":["TransformerTextInput.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Transformer } from "../Transformer.js";
|
|
4
|
+
export class CurrencyTransformer extends Transformer {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
const {
|
|
7
|
+
currency,
|
|
8
|
+
locale
|
|
9
|
+
} = options;
|
|
10
|
+
const fractionDigits = new Intl.NumberFormat(locale, {
|
|
11
|
+
style: 'currency',
|
|
12
|
+
currency
|
|
13
|
+
}).resolvedOptions().maximumFractionDigits ?? 0;
|
|
14
|
+
const cacheKey = `${locale ?? ''}|${currency}`;
|
|
15
|
+
super(({
|
|
16
|
+
value,
|
|
17
|
+
previousValue,
|
|
18
|
+
selection
|
|
19
|
+
}) => {
|
|
20
|
+
'worklet';
|
|
21
|
+
|
|
22
|
+
const isDigitAt = (s, i) => {
|
|
23
|
+
const c = s.charCodeAt(i);
|
|
24
|
+
return c >= 48 && c <= 57;
|
|
25
|
+
};
|
|
26
|
+
const prevValue = previousValue ?? value;
|
|
27
|
+
let digits = value.replace(/\D/g, '');
|
|
28
|
+
const prevDigits = prevValue.replace(/\D/g, '');
|
|
29
|
+
let cursorDigitIndex = 0;
|
|
30
|
+
for (let i = 0; i < selection.start && i < value.length; i++) {
|
|
31
|
+
if (isDigitAt(value, i)) cursorDigitIndex++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Backspace next to a separator doesn't change the digit count, so the
|
|
35
|
+
// formatter would re-add it. Drop the digit before the cursor so the
|
|
36
|
+
// keystroke has a visible effect (drops the last digit when the cursor
|
|
37
|
+
// sits past the end of the digits).
|
|
38
|
+
if (digits.length === prevDigits.length && value.length < prevValue.length && cursorDigitIndex > 0) {
|
|
39
|
+
digits = digits.slice(0, cursorDigitIndex - 1) + digits.slice(cursorDigitIndex);
|
|
40
|
+
cursorDigitIndex -= 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Treat all-zero digits as empty so leading zeros and the last-cent
|
|
44
|
+
// backspace both clear the input cleanly.
|
|
45
|
+
const amount = parseInt(digits, 10) / 10 ** fractionDigits;
|
|
46
|
+
if (!digits || amount === 0) {
|
|
47
|
+
return {
|
|
48
|
+
value: '',
|
|
49
|
+
selection: {
|
|
50
|
+
start: 0,
|
|
51
|
+
end: 0
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const cache = globalThis.__currencyFormatters ?? {};
|
|
56
|
+
let formatter = cache[cacheKey];
|
|
57
|
+
if (!formatter) {
|
|
58
|
+
formatter = new Intl.NumberFormat(locale, {
|
|
59
|
+
style: 'currency',
|
|
60
|
+
currency
|
|
61
|
+
});
|
|
62
|
+
cache[cacheKey] = formatter;
|
|
63
|
+
globalThis.__currencyFormatters = cache;
|
|
64
|
+
}
|
|
65
|
+
const formatted = formatter.format(amount);
|
|
66
|
+
|
|
67
|
+
// Map cursor to "right after the Nth digit in formatted" where N is
|
|
68
|
+
// the user's digit-cursor in the raw value, adjusted for the digit
|
|
69
|
+
// count diff between raw and formatted. parseInt may strip leading
|
|
70
|
+
// zeros (raw `0123` → formatted `123`) and the formatter may pad
|
|
71
|
+
// with cents zeros (raw `1` → formatted `001`); the diff captures
|
|
72
|
+
// both so the cursor stays right after whatever digit the user just
|
|
73
|
+
// typed (or to the right of the dropped digit, on backspace).
|
|
74
|
+
let formattedDigitsCount = 0;
|
|
75
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
76
|
+
if (isDigitAt(formatted, i)) formattedDigitsCount++;
|
|
77
|
+
}
|
|
78
|
+
const targetDigit = cursorDigitIndex + (formattedDigitsCount - digits.length);
|
|
79
|
+
let newCursor = formatted.length;
|
|
80
|
+
if (targetDigit >= formattedDigitsCount) {
|
|
81
|
+
// Past the last digit — snap to right after the last digit so the
|
|
82
|
+
// cursor sits at the end of the amount (excludes any trailing
|
|
83
|
+
// symbol like " €").
|
|
84
|
+
for (let i = formatted.length - 1; i >= 0; i--) {
|
|
85
|
+
if (isDigitAt(formatted, i)) {
|
|
86
|
+
newCursor = i + 1;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (targetDigit <= 0) {
|
|
91
|
+
// Before the first digit — snap to the first digit so the next
|
|
92
|
+
// keystroke lands inside the number rather than before any
|
|
93
|
+
// prefix symbol.
|
|
94
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
95
|
+
if (isDigitAt(formatted, i)) {
|
|
96
|
+
newCursor = i;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
let seen = 0;
|
|
102
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
103
|
+
if (isDigitAt(formatted, i)) {
|
|
104
|
+
seen++;
|
|
105
|
+
if (seen === targetDigit) {
|
|
106
|
+
newCursor = i + 1;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
value: formatted,
|
|
114
|
+
selection: {
|
|
115
|
+
start: newCursor,
|
|
116
|
+
end: newCursor
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=currency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Transformer","CurrencyTransformer","constructor","options","currency","locale","fractionDigits","Intl","NumberFormat","style","resolvedOptions","maximumFractionDigits","cacheKey","value","previousValue","selection","isDigitAt","s","i","c","charCodeAt","prevValue","digits","replace","prevDigits","cursorDigitIndex","start","length","slice","amount","parseInt","end","cache","globalThis","__currencyFormatters","formatter","formatted","format","formattedDigitsCount","targetDigit","newCursor","seen"],"sourceRoot":"../../../src","sources":["formatters/currency.ts"],"mappings":"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","ignoreList":[]}
|
package/lib/module/registry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { runOnUI } from 'react-native-worklets';
|
|
3
|
+
import { runOnUI, executeOnUIRuntimeSync } from 'react-native-worklets';
|
|
4
4
|
import NativeTransformerTextInputModule from "./NativeTransformerTextInputModule.js";
|
|
5
5
|
import { computeUncontrolledSelection, validateSelection } from "./selection.js";
|
|
6
6
|
let initialized = false;
|
|
@@ -9,8 +9,9 @@ function initializeIfNeeded() {
|
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
// Set up registry on UI runtime synchronously so it is guaranteed to exist
|
|
13
|
+
// when native code accesses it after install().
|
|
14
|
+
executeOnUIRuntimeSync(() => {
|
|
14
15
|
'worklet';
|
|
15
16
|
|
|
16
17
|
const transformersMap = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["runOnUI","NativeTransformerTextInputModule","computeUncontrolledSelection","validateSelection","initialized","initializeIfNeeded","transformersMap","Map","globalThis","__rntti_registerTransformerRegistry","register","id","transformer","set","unregister","transformerId","delete","get","install","currentId","registerTransformer","worklet","previousValue","previousSelection","transformerWrapper","value","selectionStart","selectionEnd","transform","result","selection","start","end","newValue","newSelection","length","unregisterTransformer","global"],"sourceRoot":"../../src","sources":["registry.ts"],"mappings":";;AAAA,SAASA,OAAO,QAAQ,uBAAuB;
|
|
1
|
+
{"version":3,"names":["runOnUI","executeOnUIRuntimeSync","NativeTransformerTextInputModule","computeUncontrolledSelection","validateSelection","initialized","initializeIfNeeded","transformersMap","Map","globalThis","__rntti_registerTransformerRegistry","register","id","transformer","set","unregister","transformerId","delete","get","install","currentId","registerTransformer","worklet","previousValue","previousSelection","transformerWrapper","value","selectionStart","selectionEnd","transform","result","selection","start","end","newValue","newSelection","length","unregisterTransformer","global"],"sourceRoot":"../../src","sources":["registry.ts"],"mappings":";;AAAA,SAASA,OAAO,EAAEC,sBAAsB,QAAQ,uBAAuB;AACvE,OAAOC,gCAAgC,MAAM,uCAAoC;AAEjF,SAASC,4BAA4B,EAAEC,iBAAiB,QAAQ,gBAAa;AAqB7E,IAAIC,WAAW,GAAG,KAAK;AAEvB,SAASC,kBAAkBA,CAAA,EAAG;EAC5B,IAAID,WAAW,EAAE;IACf;EACF;;EAEA;EACA;EACAJ,sBAAsB,CAAC,MAAM;IAC3B,SAAS;;IAET,MAAMM,eAAe,GAAG,IAAIC,GAAG,CAA6B,CAAC;IAE7DC,UAAU,CAACC,mCAAmC,GAAG;MAC/CC,QAAQA,CAACC,EAAE,EAAEC,WAAW,EAAE;QACxBN,eAAe,CAACO,GAAG,CAACF,EAAE,EAAEC,WAAW,CAAC;MACtC,CAAC;MACDE,UAAUA,CAACC,aAAa,EAAE;QACxBT,eAAe,CAACU,MAAM,CAACD,aAAa,CAAC;MACvC,CAAC;MACDE,GAAGA,CAACF,aAAa,EAAE;QACjB,OAAOT,eAAe,CAACW,GAAG,CAACF,aAAa,CAAC;MAC3C;IACF,CAAC;EACH,CAAC,CAAC,CAAC,CAAC;EAEJd,gCAAgC,CAACiB,OAAO,CAAC,CAAC;EAE1Cd,WAAW,GAAG,IAAI;AACpB;;AAEA;AACA,IAAIe,SAAS,GAAG,CAAC;AAEjB,OAAO,SAASC,mBAAmBA,CAACR,WAAwB,EAAU;EACpEP,kBAAkB,CAAC,CAAC;EAEpB,MAAMM,EAAE,GAAGQ,SAAS,EAAE;EACtB,MAAME,OAAO,GAAGT,WAAW,CAACS,OAAO;EAEnCtB,OAAO,CAAC,MAAM;IACZ,SAAS;;IAET,IAAIuB,aAA4B,GAAG,IAAI;IACvC,IAAIC,iBAAmC,GAAG,IAAI;IAE9C,MAAMC,kBAAsC,GAAGA,CAC7CC,KAAK,EACLC,cAAc,EACdC,YAAY,EACZC,SAAS,KACN;MACH,MAAMC,MAAM,GAAGD,SAAS,GACpBP,OAAO,CAAC;QACNI,KAAK;QACLH,aAAa,EAAEA,aAAa,IAAIG,KAAK;QACrCK,SAAS,EAAE;UAAEC,KAAK,EAAEL,cAAc;UAAEM,GAAG,EAAEL;QAAa,CAAC;QACvDJ,iBAAiB,EAAEA,iBAAiB,IAAI;UACtCQ,KAAK,EAAEL,cAAc;UACrBM,GAAG,EAAEL;QACP;MACF,CAAC,CAAC,GACF,IAAI;MACR,MAAMM,QAAQ,GAAGJ,MAAM,EAAEJ,KAAK,IAAIA,KAAK;MACvC,IAAIS,YAAuB;MAC3B,IAAIL,MAAM,EAAEC,SAAS,IAAI,IAAI,EAAE;QAC7BI,YAAY,GAAGL,MAAM,CAACC,SAAS;QAC/B3B,iBAAiB,CAAC+B,YAAY,EAAED,QAAQ,CAACE,MAAM,CAAC;MAClD,CAAC,MAAM;QACLD,YAAY,GAAGhC,4BAA4B,CACzCuB,KAAK,EACLQ,QAAQ,EACRP,cAAc,EACdC,YACF,CAAC;MACH;MACAL,aAAa,GAAGW,QAAQ;MACxBV,iBAAiB,GAAGW,YAAY;MAChC,OAAO;QACLT,KAAK,EAAEQ,QAAQ;QACfH,SAAS,EAAEI;MACb,CAAC;IACH,CAAC;IAED1B,UAAU,CAACC,mCAAmC,EAAEC,QAAQ,CACtDC,EAAE,EACFa,kBACF,CAAC;EACH,CAAC,CAAC,CAAC,CAAC;EAEJ,OAAOb,EAAE;AACX;AAEA,OAAO,SAASyB,qBAAqBA,CAACrB,aAAqB,EAAE;EAC3DhB,OAAO,CAAC,MAAM;IACZ,SAAS;;IAETsC,MAAM,CAAC5B,mCAAmC,EAAEK,UAAU,CAACC,aAAa,CAAC;EACvE,CAAC,CAAC,CAAC,CAAC;AACN","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransformerTextInput.d.ts","sourceRoot":"","sources":["../../../src/TransformerTextInput.tsx"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAQjE,KAAK,mCAAmC,GAAG;IACzC;;OAEG;IACH,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB;;OAEG;IACH,MAAM,EAAE,CAAC,OAAO,EAAE;QAChB;;WAEG;QACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB;;WAEG;QACH,SAAS,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C;;WAEG;QACH,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,KAAK,IAAI,CAAC;IACX;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,YAAY,GACrD,mCAAmC,CAAC;AAEtC,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAAG;IACtE;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"TransformerTextInput.d.ts","sourceRoot":"","sources":["../../../src/TransformerTextInput.tsx"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAQjE,KAAK,mCAAmC,GAAG;IACzC;;OAEG;IACH,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB;;OAEG;IACH,MAAM,EAAE,CAAC,OAAO,EAAE;QAChB;;WAEG;QACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB;;WAEG;QACH,SAAS,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C;;WAEG;QACH,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,KAAK,IAAI,CAAC;IACX;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,YAAY,GACrD,mCAAmC,CAAC;AAEtC,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,GAAG;IACtE;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4G6imB,CAAC;;;;;;;;;;IAlH7kmB;;OAEG;iBACU,WAAW;gEA0GzB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Transformer } from '../Transformer';
|
|
2
|
+
export type CurrencyTransformerOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* ISO 4217 currency code, e.g. 'USD', 'EUR', 'JPY'. Drives the symbol and
|
|
5
|
+
* the number of fraction digits.
|
|
6
|
+
*/
|
|
7
|
+
currency: string;
|
|
8
|
+
/**
|
|
9
|
+
* BCP 47 locale tag, e.g. 'en-US', 'de-DE'. Drives separators and symbol
|
|
10
|
+
* position. Defaults to the runtime's default locale.
|
|
11
|
+
*/
|
|
12
|
+
locale?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare class CurrencyTransformer extends Transformer {
|
|
15
|
+
constructor(options: CurrencyTransformerOptions);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.d.ts","sourceRoot":"","sources":["../../../../src/formatters/currency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,mBAAoB,SAAQ,WAAW;gBACtC,OAAO,EAAE,0BAA0B;CA6HhD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjE,KAAK,kBAAkB,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,KACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AAE7C,KAAK,uCAAuC,GAAG;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC5D,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;CAC5D,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,mCAAmC,EACnC,uCAAuC,GACvC,SAAS,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjE,KAAK,kBAAkB,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,OAAO,KACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC;AAE7C,KAAK,uCAAuC,GAAG;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC5D,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;CAC5D,CAAC;AAEF,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,mCAAmC,EACnC,uCAAuC,GACvC,SAAS,CAAC;CACf;AAqCD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAyDpE;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,QAM1D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-transformer-text-input",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "TextInput component that allows transforming text synchronously with a worklet",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"types": "./lib/typescript/src/formatters/pattern.d.ts",
|
|
21
21
|
"default": "./lib/module/formatters/pattern.js"
|
|
22
22
|
},
|
|
23
|
+
"./formatters/currency": {
|
|
24
|
+
"source": "./src/formatters/currency.ts",
|
|
25
|
+
"types": "./lib/typescript/src/formatters/currency.d.ts",
|
|
26
|
+
"default": "./lib/module/formatters/currency.js"
|
|
27
|
+
},
|
|
23
28
|
"./formatters/phone-data": {
|
|
24
29
|
"source": "./src/formatters/phone-data.ts",
|
|
25
30
|
"types": "./lib/typescript/src/formatters/phone-data.d.ts",
|
|
@@ -61,13 +61,34 @@ export type TransformerTextInputProps = Omit<TextInputProps, 'value'> & {
|
|
|
61
61
|
|
|
62
62
|
export const TransformerTextInput = forwardRef(
|
|
63
63
|
(
|
|
64
|
-
{
|
|
64
|
+
{
|
|
65
|
+
transformer,
|
|
66
|
+
onChangeText,
|
|
67
|
+
defaultValue,
|
|
68
|
+
...others
|
|
69
|
+
}: TransformerTextInputProps,
|
|
65
70
|
forwardedRef: Ref<TransformerTextInputInstance>,
|
|
66
71
|
) => {
|
|
67
72
|
const transformerId = useMemo(() => {
|
|
68
73
|
return registerTransformer(transformer);
|
|
69
74
|
}, [transformer]);
|
|
70
75
|
|
|
76
|
+
// Pre-transform defaultValue on the JS thread so Yoga measures the correct
|
|
77
|
+
// text from the start. Without this the native-side transformation happens
|
|
78
|
+
// after layout and doesn't trigger a remeasure.
|
|
79
|
+
const transformedDefaultValue = useMemo(() => {
|
|
80
|
+
if (defaultValue == null) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const result = transformer.worklet({
|
|
84
|
+
value: defaultValue,
|
|
85
|
+
previousValue: defaultValue,
|
|
86
|
+
selection: { start: defaultValue.length, end: defaultValue.length },
|
|
87
|
+
previousSelection: { start: 0, end: 0 },
|
|
88
|
+
});
|
|
89
|
+
return result?.value ?? defaultValue;
|
|
90
|
+
}, [defaultValue, transformer]);
|
|
91
|
+
|
|
71
92
|
useEffect(() => {
|
|
72
93
|
return () => {
|
|
73
94
|
unregisterTransformer(transformerId);
|
|
@@ -135,6 +156,7 @@ export const TransformerTextInput = forwardRef(
|
|
|
135
156
|
// @ts-expect-error
|
|
136
157
|
ref={inputRef}
|
|
137
158
|
onChangeText={handleChangeText}
|
|
159
|
+
defaultValue={transformedDefaultValue}
|
|
138
160
|
{...others}
|
|
139
161
|
/>
|
|
140
162
|
</TransformerTextInputDecoratorViewNativeComponent>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Transformer } from '../Transformer';
|
|
2
|
+
|
|
3
|
+
export type CurrencyTransformerOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* ISO 4217 currency code, e.g. 'USD', 'EUR', 'JPY'. Drives the symbol and
|
|
6
|
+
* the number of fraction digits.
|
|
7
|
+
*/
|
|
8
|
+
currency: string;
|
|
9
|
+
/**
|
|
10
|
+
* BCP 47 locale tag, e.g. 'en-US', 'de-DE'. Drives separators and symbol
|
|
11
|
+
* position. Defaults to the runtime's default locale.
|
|
12
|
+
*/
|
|
13
|
+
locale?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class CurrencyTransformer extends Transformer {
|
|
17
|
+
constructor(options: CurrencyTransformerOptions) {
|
|
18
|
+
const { currency, locale } = options;
|
|
19
|
+
|
|
20
|
+
const fractionDigits =
|
|
21
|
+
new Intl.NumberFormat(locale, {
|
|
22
|
+
style: 'currency',
|
|
23
|
+
currency,
|
|
24
|
+
}).resolvedOptions().maximumFractionDigits ?? 0;
|
|
25
|
+
const cacheKey = `${locale ?? ''}|${currency}`;
|
|
26
|
+
|
|
27
|
+
super(({ value, previousValue, selection }) => {
|
|
28
|
+
'worklet';
|
|
29
|
+
|
|
30
|
+
const isDigitAt = (s: string, i: number) => {
|
|
31
|
+
const c = s.charCodeAt(i);
|
|
32
|
+
return c >= 48 && c <= 57;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const prevValue = previousValue ?? value;
|
|
36
|
+
let digits = value.replace(/\D/g, '');
|
|
37
|
+
const prevDigits = prevValue.replace(/\D/g, '');
|
|
38
|
+
|
|
39
|
+
let cursorDigitIndex = 0;
|
|
40
|
+
for (let i = 0; i < selection.start && i < value.length; i++) {
|
|
41
|
+
if (isDigitAt(value, i)) cursorDigitIndex++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Backspace next to a separator doesn't change the digit count, so the
|
|
45
|
+
// formatter would re-add it. Drop the digit before the cursor so the
|
|
46
|
+
// keystroke has a visible effect (drops the last digit when the cursor
|
|
47
|
+
// sits past the end of the digits).
|
|
48
|
+
if (
|
|
49
|
+
digits.length === prevDigits.length &&
|
|
50
|
+
value.length < prevValue.length &&
|
|
51
|
+
cursorDigitIndex > 0
|
|
52
|
+
) {
|
|
53
|
+
digits =
|
|
54
|
+
digits.slice(0, cursorDigitIndex - 1) +
|
|
55
|
+
digits.slice(cursorDigitIndex);
|
|
56
|
+
cursorDigitIndex -= 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Treat all-zero digits as empty so leading zeros and the last-cent
|
|
60
|
+
// backspace both clear the input cleanly.
|
|
61
|
+
const amount = parseInt(digits, 10) / 10 ** fractionDigits;
|
|
62
|
+
if (!digits || amount === 0) {
|
|
63
|
+
return { value: '', selection: { start: 0, end: 0 } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cache: Record<string, Intl.NumberFormat> =
|
|
67
|
+
(
|
|
68
|
+
globalThis as unknown as {
|
|
69
|
+
__currencyFormatters?: Record<string, Intl.NumberFormat>;
|
|
70
|
+
}
|
|
71
|
+
).__currencyFormatters ?? {};
|
|
72
|
+
let formatter = cache[cacheKey];
|
|
73
|
+
if (!formatter) {
|
|
74
|
+
formatter = new Intl.NumberFormat(locale, {
|
|
75
|
+
style: 'currency',
|
|
76
|
+
currency,
|
|
77
|
+
});
|
|
78
|
+
cache[cacheKey] = formatter;
|
|
79
|
+
(
|
|
80
|
+
globalThis as unknown as {
|
|
81
|
+
__currencyFormatters?: Record<string, Intl.NumberFormat>;
|
|
82
|
+
}
|
|
83
|
+
).__currencyFormatters = cache;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const formatted = formatter.format(amount);
|
|
87
|
+
|
|
88
|
+
// Map cursor to "right after the Nth digit in formatted" where N is
|
|
89
|
+
// the user's digit-cursor in the raw value, adjusted for the digit
|
|
90
|
+
// count diff between raw and formatted. parseInt may strip leading
|
|
91
|
+
// zeros (raw `0123` → formatted `123`) and the formatter may pad
|
|
92
|
+
// with cents zeros (raw `1` → formatted `001`); the diff captures
|
|
93
|
+
// both so the cursor stays right after whatever digit the user just
|
|
94
|
+
// typed (or to the right of the dropped digit, on backspace).
|
|
95
|
+
let formattedDigitsCount = 0;
|
|
96
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
97
|
+
if (isDigitAt(formatted, i)) formattedDigitsCount++;
|
|
98
|
+
}
|
|
99
|
+
const targetDigit =
|
|
100
|
+
cursorDigitIndex + (formattedDigitsCount - digits.length);
|
|
101
|
+
|
|
102
|
+
let newCursor = formatted.length;
|
|
103
|
+
if (targetDigit >= formattedDigitsCount) {
|
|
104
|
+
// Past the last digit — snap to right after the last digit so the
|
|
105
|
+
// cursor sits at the end of the amount (excludes any trailing
|
|
106
|
+
// symbol like " €").
|
|
107
|
+
for (let i = formatted.length - 1; i >= 0; i--) {
|
|
108
|
+
if (isDigitAt(formatted, i)) {
|
|
109
|
+
newCursor = i + 1;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else if (targetDigit <= 0) {
|
|
114
|
+
// Before the first digit — snap to the first digit so the next
|
|
115
|
+
// keystroke lands inside the number rather than before any
|
|
116
|
+
// prefix symbol.
|
|
117
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
118
|
+
if (isDigitAt(formatted, i)) {
|
|
119
|
+
newCursor = i;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
let seen = 0;
|
|
125
|
+
for (let i = 0; i < formatted.length; i++) {
|
|
126
|
+
if (isDigitAt(formatted, i)) {
|
|
127
|
+
seen++;
|
|
128
|
+
if (seen === targetDigit) {
|
|
129
|
+
newCursor = i + 1;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
value: formatted,
|
|
138
|
+
selection: { start: newCursor, end: newCursor },
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/registry.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runOnUI } from 'react-native-worklets';
|
|
1
|
+
import { runOnUI, executeOnUIRuntimeSync } from 'react-native-worklets';
|
|
2
2
|
import NativeTransformerTextInputModule from './NativeTransformerTextInputModule';
|
|
3
3
|
import { type Selection, type Transformer } from './Transformer';
|
|
4
4
|
import { computeUncontrolledSelection, validateSelection } from './selection';
|
|
@@ -29,8 +29,9 @@ function initializeIfNeeded() {
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// Set up registry on UI runtime synchronously so it is guaranteed to exist
|
|
33
|
+
// when native code accesses it after install().
|
|
34
|
+
executeOnUIRuntimeSync(() => {
|
|
34
35
|
'worklet';
|
|
35
36
|
|
|
36
37
|
const transformersMap = new Map<number, TransformerWrapper>();
|