Skip to content

Patch for using a custom number formatter inside ... #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ajp8164 opened this issue Jan 30, 2024 · 2 comments
Open

Patch for using a custom number formatter inside ... #33

ajp8164 opened this issue Jan 30, 2024 · 2 comments

Comments

@ajp8164
Copy link

ajp8164 commented Jan 30, 2024

This patch adds a new property called customFormatter: (value: number) => string;. If present it replaces the built-in formatter. The value input is the unformatted number value in the component. The output string is sent to the onChangeText() callback. Properties that are used outside of the built-in formatter are still enforced (e.g. precision, maxValue, maxLength, ...)

I can issue a PR if there is interest.

diff --git a/node_modules/react-native-currency-input/lib/typescript/src/props.d.ts b/node_modules/react-native-currency-input/lib/typescript/src/props.d.ts
index 9fcef7a..d6c46b4 100644
--- a/node_modules/react-native-currency-input/lib/typescript/src/props.d.ts
+++ b/node_modules/react-native-currency-input/lib/typescript/src/props.d.ts
@@ -1,118 +1,125 @@
 /// <reference types="react" />
-import type { TextInputProps, StyleProp, ViewStyle, TextProps, TextStyle } from 'react-native';
+
+import type { StyleProp, TextInputProps, TextProps, TextStyle, ViewStyle } from 'react-native';
+
 export interface FormatNumberOptions {
-    /**
-     * Character for thousands delimiter.
-     */
-    delimiter?: string;
-    /**
-     * Set this to `true` to disable negative values.
-     */
-    ignoreNegative?: boolean;
-    /**
-     * Decimal precision. Defaults to 2.
-     */
-    precision?: number;
-    /**
-     * Decimal separator character.
-     */
-    separator?: string;
-    /**
-     * Character to be prefixed on the value.
-     */
-    prefix?: string;
-    /**
-     * Character to be suffixed on the value.
-     */
-    suffix?: string;
-    /**
-     * Set this to `true` to show the `+` character on positive values.
-     */
-    showPositiveSign?: boolean;
-    /**
-     * Where the negative/positive sign (+/-) should be placed. Defaults to "afterPrefix".
-     * Use `showPositiveSign` if you want to show the `+` sign.
-     */
-    signPosition?: 'beforePrefix' | 'afterPrefix';
+  /**
+   * Character for thousands delimiter.
+   */
+  delimiter?: string;
+  /**
+   * Set this to `true` to disable negative values.
+   */
+  ignoreNegative?: boolean;
+  /**
+   * Decimal precision. Defaults to 2.
+   */
+  precision?: number;
+  /**
+   * Decimal separator character.
+   */
+  separator?: string;
+  /**
+   * Character to be prefixed on the value.
+   */
+  prefix?: string;
+  /**
+   * Character to be suffixed on the value.
+   */
+  suffix?: string;
+  /**
+   * Set this to `true` to show the `+` character on positive values.
+   */
+  showPositiveSign?: boolean;
+  /**
+   * Where the negative/positive sign (+/-) should be placed. Defaults to "afterPrefix".
+   * Use `showPositiveSign` if you want to show the `+` sign.
+   */
+  signPosition?: 'beforePrefix' | 'afterPrefix';
 }
 export interface CurrencyInputProps extends Omit<TextInputProps, 'value'> {
-    renderTextInput?: (props: TextInputProps) => JSX.Element;
-    /**
-     * Character for thousands delimiter.
-     */
-    delimiter?: string;
-    /**
-     * Max value allowed on input.
-     * Notice that this might cause unexpected behavior if you pass a value higher than this on input `value`. In that case, consider do your own validation instead of using this property
-     */
-    maxValue?: number;
-    /**
-     * Min value allowed on input.
-     * Notice that this might cause unexpected behavior if you pass a value lower than this on input `value`. In that case, consider do your own validation instead of using this property
-     */
-    minValue?: number;
-    /**
-     * Callback that is called when the input's value changes.
-     * @param value The number value.
-     */
-    onChangeValue?(value: number | null): void;
-    /**
-     * Decimal precision. Defaults to 2.
-     */
-    precision?: number;
-    /**
-     * Decimal separator character.
-     */
-    separator?: string;
-    /**
-     * Character to be prefixed on the value.
-     */
-    prefix?: string;
-    /**
-     * Character to be suffixed on the value.
-     */
-    suffix?: string;
-    /**
-     * @deprecated. Use `prefix` instead.
-     */
-    unit?: string;
-    /**
-     * The number value of the input.
-     * IMPORTANT: This is used to control the component, but keep in mind that this is not the final `value` property of the `TextInput`
-     */
-    value: number | null;
-    /**
-     * Set this to `true` to show the `+` character on positive values.
-     */
-    showPositiveSign?: boolean;
-    /**
-     * Where the negative/positive sign (+/-) should be placed. Defaults to "afterPrefix".
-     * Use `showPositiveSign` if you want to show the `+` sign.
-     */
-    signPosition?: 'beforePrefix' | 'afterPrefix';
+  renderTextInput?: (props: TextInputProps) => JSX.Element;
+  /**
+   * Replaces the built-in formatter. This callback is called after input changes and before onChangeValue().
+   * @param value The number value.
+   */
+  customFormatter?(value: number): string;
+  /**
+   * Character for thousands delimiter.
+   */
+  delimiter?: string;
+  /**
+   * Max value allowed on input.
+   * Notice that this might cause unexpected behavior if you pass a value higher than this on input `value`. In that case, consider do your own validation instead of using this property
+   */
+  maxValue?: number;
+  /**
+   * Min value allowed on input.
+   * Notice that this might cause unexpected behavior if you pass a value lower than this on input `value`. In that case, consider do your own validation instead of using this property
+   */
+  minValue?: number;
+  /**
+   * Callback that is called when the input's value changes.
+   * @param value The number value.
+   */
+  onChangeValue?(value: number | null): void;
+  /**
+   * Decimal precision. Defaults to 2.
+   */
+  precision?: number;
+  /**
+   * Decimal separator character.
+   */
+  separator?: string;
+  /**
+   * Character to be prefixed on the value.
+   */
+  prefix?: string;
+  /**
+   * Character to be suffixed on the value.
+   */
+  suffix?: string;
+  /**
+   * @deprecated. Use `prefix` instead.
+   */
+  unit?: string;
+  /**
+   * The number value of the input.
+   * IMPORTANT: This is used to control the component, but keep in mind that this is not the final `value` property of the `TextInput`
+   */
+  value: number | null;
+  /**
+   * Set this to `true` to show the `+` character on positive values.
+   */
+  showPositiveSign?: boolean;
+  /**
+   * Where the negative/positive sign (+/-) should be placed. Defaults to "afterPrefix".
+   * Use `showPositiveSign` if you want to show the `+` sign.
+   */
+  signPosition?: 'beforePrefix' | 'afterPrefix';
 }
 export interface FakeCurrencyInputProps extends CurrencyInputProps {
-    /**
-     * Style for the container View that wraps the Text
-     */
-    containerStyle?: StyleProp<ViewStyle>;
-    /**
-     * Color of the caret. Defaults to #6495ed
-     */
-    caretColor?: string;
-    /**
-     * Style for the caret text component
-     */
-    caretStyle?: StyleProp<TextStyle>;
+  /**
+   * Style for the container View that wraps the Text
+   */
+  containerStyle?: StyleProp<ViewStyle>;
+  /**
+   * Color of the caret. Defaults to #6495ed
+   */
+  caretColor?: string;
+  /**
+   * Style for the caret text component
+   */
+  caretStyle?: StyleProp<TextStyle>;
 }
 export interface TextWithCursorProps extends TextProps {
-    children?: React.ReactNode;
-    /**
-     * Show or hides the cursor. Defaults to false
-     */
-    cursorVisible?: boolean;
-    /**
-     * Props for the cursor. Use this to set a custom `style` prop.
-     */
-    cursorProps?: TextProps;
+  children?: React.ReactNode;
+  /**
+   * Show or hides the cursor. Defaults to false
+   */
+  cursorVisible?: boolean;
+  /**
+   * Props for the cursor. Use this to set a custom `style` prop.
+   */
+  cursorProps?: TextProps;
 }
diff --git a/node_modules/react-native-currency-input/src/CurrencyInput.tsx b/node_modules/react-native-currency-input/src/CurrencyInput.tsx
index 6c7e844..39e8c4f 100644
--- a/node_modules/react-native-currency-input/src/CurrencyInput.tsx
+++ b/node_modules/react-native-currency-input/src/CurrencyInput.tsx
@@ -15,6 +16,7 @@ export const CurrencyInput = React.forwardRef<TextInput, CurrencyInputProps>(fun
     value,
     onChangeText,
     onChangeValue,
+    customFormatter,
     separator,
     delimiter,
     prefix = '',
@@ -34,16 +35,20 @@ export const CurrencyInput = React.forwardRef<TextInput, CurrencyInputProps>(fun
 
   const formattedValue = React.useMemo(() => {
     if (!!value || value === 0 || value === -0) {
-      return formatNumber(value, {
-        separator,
-        prefix,
-        suffix,
-        precision,
-        delimiter,
-        ignoreNegative: noNegativeValues,
-        signPosition,
-        showPositiveSign,
-      });
+      if (customFormatter) {
+        return customFormatter(value);
+      } else {
+        return formatNumber(value, {
+          separator,
+          prefix,
+          suffix,
+          precision,
+          delimiter,
+          ignoreNegative: noNegativeValues,
+          signPosition,
+          showPositiveSign,
+        });
+      }
     } else {
       return '';
     }
@@ -120,7 +125,7 @@ export const CurrencyInput = React.forwardRef<TextInput, CurrencyInputProps>(fun
 
       const zerosOnValue = textNumericValue.replace(/[^0]/g, '').length;
 
-      let newValue: number | null;
+      let newValue: number | null = numberValue;
 
       if (!textNumericValue || (!numberValue && zerosOnValue === precision)) {
         // Allow to clean the value instead of beign 0
diff --git a/node_modules/react-native-currency-input/src/props.ts b/node_modules/react-native-currency-input/src/props.ts
index 0c2f069..ece33fe 100644
--- a/node_modules/react-native-currency-input/src/props.ts
+++ b/node_modules/react-native-currency-input/src/props.ts
@@ -51,6 +51,12 @@ export interface FormatNumberOptions {
 
 export interface CurrencyInputProps extends Omit<TextInputProps, 'value'> {
   renderTextInput?: (props: TextInputProps) => JSX.Element;
+  /**
+   * Replaces the built-in formatter. This callback is called after input changes and before onChangeValue().
+   * @param value The number value.
+   */
+  customFormatter?(value: number): string;
+
   /**
    * Character for thousands delimiter.
    */
@CaioQuirinoMedeiros
Copy link
Owner

@ajp8164 Thanks for your contribution but... the whole point of this lib is the formatNumber function, why would you want to use a customFormatter? If someone needs a custom formatter, why not just use it with the standard TextInput?

@ajp8164
Copy link
Author

ajp8164 commented Jan 31, 2024

It's a fair point. I've already turtled this lib into my stack and adding this small extension saves a lot of work having to extend other middleware libs. I may reconsider my implementation but this is cheaper and faster for sure. Better? idk. One use case is RTL entry for hh:mm:ss.

UPDATE - reviewing the implementation of this library and looking at some of the available masking libraries I believe this is the best library for my use cases (just sort of a nit that this library is named and focused on only currency). If this library was forked and renamed for more general formatting then I can see multiple built-in formatters plus a custom callback. The RTL input (TextWithCursor) is a nice feature. Using this patch I added a formatter as follows to handle entering sec, min, then hrs. With this formatter I get a mask input as well.

export const maskToHMS = (value: number) => {
  return value.toString().padStart(6, '0').split(/(?=(?:..)*$)/).join(':');
};

Anyway, I like the library! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants