react-native-transformer-text-input 0.1.0-alpha.4 → 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.
Files changed (30) hide show
  1. package/README.md +17 -1
  2. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorView.kt +30 -0
  3. package/cpp/TransformerTextInputDecoratorViewShadowNode.cpp +24 -1
  4. package/ios/TransformerTextInputDecoratorView.mm +42 -3
  5. package/lib/module/TransformerTextInput.js +24 -0
  6. package/lib/module/TransformerTextInput.js.map +1 -1
  7. package/lib/module/formatters/currency.js +122 -0
  8. package/lib/module/formatters/currency.js.map +1 -0
  9. package/lib/module/formatters/phone-data.js +4844 -0
  10. package/lib/module/formatters/phone-data.js.map +1 -0
  11. package/lib/module/formatters/phone-number.js +323 -61
  12. package/lib/module/formatters/phone-number.js.map +1 -1
  13. package/lib/module/registry.js +4 -3
  14. package/lib/module/registry.js.map +1 -1
  15. package/lib/typescript/scripts/generate-phone-data.d.ts +7 -0
  16. package/lib/typescript/scripts/generate-phone-data.d.ts.map +1 -0
  17. package/lib/typescript/src/TransformerTextInput.d.ts.map +1 -1
  18. package/lib/typescript/src/formatters/currency.d.ts +17 -0
  19. package/lib/typescript/src/formatters/currency.d.ts.map +1 -0
  20. package/lib/typescript/src/formatters/phone-data.d.ts +15 -0
  21. package/lib/typescript/src/formatters/phone-data.d.ts.map +1 -0
  22. package/lib/typescript/src/formatters/phone-number.d.ts +10 -4
  23. package/lib/typescript/src/formatters/phone-number.d.ts.map +1 -1
  24. package/lib/typescript/src/registry.d.ts.map +1 -1
  25. package/package.json +14 -1
  26. package/src/TransformerTextInput.tsx +23 -1
  27. package/src/formatters/currency.ts +142 -0
  28. package/src/formatters/phone-data.ts +5665 -0
  29. package/src/formatters/phone-number.ts +326 -68
  30. package/src/registry.ts +4 -3
package/README.md CHANGED
@@ -1,7 +1,23 @@
1
- # React Native Transformer Text Input
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
- ConcreteViewShadowNode::layout(layoutContext);
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
- if (oldViewProps.transformerId != newViewProps.transformerId) {
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
@@ -150,7 +178,11 @@ struct RNTTITextState {
150
178
  [textInputComponentView valueForKey:@"_backedTextInputView"];
151
179
 
152
180
  _backedTextInput = backedTextInputView;
153
- _baseDelegate = backedTextInputView.textInputDelegate;
181
+ id<RCTBackedTextInputDelegate> currentDelegate = backedTextInputView.textInputDelegate;
182
+ // Guard against setting ourselves as baseDelegate (would cause infinite recursion)
183
+ if (currentDelegate != self) {
184
+ _baseDelegate = currentDelegate;
185
+ }
154
186
  backedTextInputView.textInputDelegate = self;
155
187
 
156
188
  _observersAdded = true;
@@ -158,6 +190,9 @@ struct RNTTITextState {
158
190
 
159
191
  - (void)removeTextInputObservers
160
192
  {
193
+ if (_backedTextInput && _baseDelegate) {
194
+ _backedTextInput.textInputDelegate = _baseDelegate;
195
+ }
161
196
  _backedTextInput = nil;
162
197
  _baseDelegate = nil;
163
198
  _observersAdded = false;
@@ -204,7 +239,11 @@ struct RNTTITextState {
204
239
 
205
240
  - (BOOL)textInputShouldBeginEditing
206
241
  {
207
- return [_baseDelegate textInputShouldBeginEditing];
242
+ id<RCTBackedTextInputDelegate> delegate = _baseDelegate;
243
+ if (delegate == nil || delegate == self) {
244
+ return YES;
245
+ }
246
+ return [delegate textInputShouldBeginEditing];
208
247
  }
209
248
 
210
249
  - (nonnull NSString *)textInputShouldChangeText:(nonnull NSString *)text inRange:(NSRange)range
@@ -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","decoratorRef","textRef","setInputRef","instance","Object","assign","getValue","current","update","value","selection","transform","nativeRef","newSelection","newValue","length","start","end","clear","inputRef","handleChangeText","text","ref","style","styles","decorator","children","create","display"],"sourceRoot":"../../src","sources":["TransformerTextInput.tsx"],"mappings":"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","ignoreList":[]}
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":[]}