Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ For applications requiring conditional or more complex formatting, this package
| `autoSkip` | `boolean` | Automatically skips to the next input field when the current one is filled. Default is `false`. |
| `isRTL` | `boolean` | Enables right-to-left (RTL) text direction for the text input. Default is `false`. |
| `affinityCalculationStrategy` | `AFFINITY_CALCULATION_STRATEGY` | Defines the strategy for affinity calculation, determining how the best mask format is selected based on input. |
| `customTransformation` | `CustomTransformation` | Custom transformation applied to the text input to define how the input text should be transformed. |
| `transformation` | `Transformation` | A transformation applied to the text input to define which symbols should be transformed in which symbols. |
| `defaultValue` | `string` | The default value for the input field. |
| `value` | `string` | Current value of the input field, allowing controlled input behavior. |
| `allowSuggestions` | `boolean` (iOS only) | Enables or disables input suggestions on iOS. Default is `false`. |
Expand Down
19 changes: 16 additions & 3 deletions apps/example/src/screens/Main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const Main = () => {
const inputRef = React.useRef<TextInput>(null);

const [textState, setTextState] = React.useState({
extracted: '0000',
formatted: '00-00',
extracted: '',
formatted: '',
});

const [focused, setFocused] = React.useState(false);
Expand Down Expand Up @@ -79,7 +79,20 @@ const Main = () => {
style={styles.maskedTextInput}
onChangeText={onChangeText}
onTailPlaceholderChange={console.log}
mask="[00]-[$$]-[00]"
mask="[0999999].[09]"
// - mom, can we have a neural network at home?
// - we already have a neural network at home.
// neural network at home:
affinityFormat={[
'[0].[00]',
'[00].[00]',
'[000].[00]',
'[0000].[00]',
'[00000].[00]',
'[000000].[00]',
'[0000000].[00]',
]}
transformation={[{ set: ',', to: '.' }]}
autocomplete={false}
allowSuggestions={true}
autocompleteOnFocus={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ class AdvancedTextInputMaskDecoratorViewManager(
view.setAutoComplete(autocomplete)
}

@ReactProp(name = "customTransformation")
override fun setCustomTransformation(
@ReactProp(name = "transformation")
override fun setTransformation(
view: AdvancedTextInputMaskDecoratorView,
customTransformation: ReadableMap?,
transformation: ReadableMap?,
) {
view.setCustomTransformationMethod(customTransformation)
view.setTransformationMethod(transformation)
}

@ReactProp(name = "allowedKeys")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export type OnAdvancedMaskTextChange = Readonly<{
tailPlaceholder: string;
}>;

type CustomTransformation = {
transformationChar: string;
transformationString: string;
type Transformation = {
set: string;
to: string;
};

type Notation = {
Expand All @@ -40,7 +40,7 @@ export interface NativeProps extends ViewProps {
autocompleteOnFocus?: boolean;
autoSkip?: boolean;
customNotations?: ReadonlyArray<Notation>;
customTransformation?: Readonly<CustomTransformation>;
transformation?: Readonly<Transformation[]>;
allowedKeys?: string;
defaultValue?: string;
isRTL?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions package/src/native/views/MaskedTextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const MaskedTextInput = forwardRef<TextInput, MaskedTextInputProps>(
autocompleteOnFocus,
autoSkip,
customNotations,
customTransformation,
transformation,
allowedKeys,
defaultValue,
isRTL,
Expand Down Expand Up @@ -58,7 +58,7 @@ const MaskedTextInput = forwardRef<TextInput, MaskedTextInputProps>(
autocompleteOnFocus={autocompleteOnFocus}
autoSkip={autoSkip}
customNotations={customNotations}
customTransformation={customTransformation}
transformation={transformation}
allowedKeys={allowedKeys}
defaultValue={defaultValue}
isRTL={isRTL}
Expand Down
12 changes: 6 additions & 6 deletions package/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export type Notation = {
isOptional: boolean;
};

type CustomTransformation = {
transformationChar: string;
transformationString: string;
export type Transformation = {
set: string;
to: string;
};

export type MaskedTextInputOwnProps = {
Expand Down Expand Up @@ -72,10 +72,10 @@ export type MaskedTextInputOwnProps = {
*/
affinityCalculationStrategy?: AFFINITY_CALCULATION_STRATEGY;
/**
* Custom transformation to be applied to the text input.
* Defines how the input text should be transformed.
* Transformation to be applied to the text input.
* Defines how the inputted characters should be transformed.
*/
customTransformation?: CustomTransformation;
transformation?: Transformation[];
/**
* A string representing all symbols that can be entered in the text input.
* For example: "0123456789".
Expand Down
34 changes: 26 additions & 8 deletions package/src/web/AdvancedTextInputMaskListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Mask from './helper/Mask';
import RTLMask from './helper/RTLMask';
import CaretString from './model/CaretString';
import { CaretGravityType } from './model/types';
import type { Notation } from '../types';
import type { Notation, Transformation } from '../types';
import type { MaskResult } from './model/types';
import { AFFINITY_CALCULATION_STRATEGY } from '../enums';
import { calculateAffinityOfMask } from './helper/affinityCalculationStrategy';
Expand All @@ -26,6 +26,7 @@ class MaskedTextChangedListener {
public rightToLeft: boolean;
public textField: Field | null = null;
public allowedKeys: string;
public transformation: Transformation[];

private afterText: string = '';

Expand All @@ -37,7 +38,8 @@ class MaskedTextChangedListener {
autocomplete: boolean = true,
autoskip: boolean = false,
rightToLeft: boolean = false,
allowedKeys: string = ''
allowedKeys: string = '',
transformation: Transformation[] = []
) {
this.primaryFormat = primaryFormat;
this.affineFormats = affineFormats;
Expand All @@ -47,6 +49,7 @@ class MaskedTextChangedListener {
this.autoskip = autoskip;
this.rightToLeft = rightToLeft;
this.allowedKeys = allowedKeys;
this.transformation = transformation;
}

public get primaryMask(): Mask {
Expand All @@ -58,9 +61,7 @@ class MaskedTextChangedListener {
return;
}

const newText = this.allowedKeys
? [...text].filter((char) => this.allowedKeys.includes(char)).join('')
: text;
const newText = this.prepareText(text);

const useAutocomplete = autocomplete ?? this.autocomplete;
const textAndCaret = new CaretString(newText, newText.length, {
Expand Down Expand Up @@ -112,6 +113,25 @@ class MaskedTextChangedListener {
this.textField = textField;
};

/**
* Exclude undesired characters and apply `transformation` to the text.
*/
public prepareText = (text: string) => {
return (
[...text]
// filter out characters not in allowedKeys (if allowedKeys is defined)
.filter((char) => !this.allowedKeys || this.allowedKeys.includes(char))
// replace each character according to our transformation
.map((char) => {
const match = this.transformation.find(({ set }) =>
set.includes(char)
);
return match ? match.to : char;
})
.join('')
);
};

public handleTextChange = (
event: NativeSyntheticEvent<TextInputChangeEventData>
): MaskResult => {
Expand All @@ -135,9 +155,7 @@ class MaskedTextChangedListener {
autoskip: useAutoskip,
autocomplete: useAutocomplete,
};
const newText = this.allowedKeys
? [...text].filter((char) => this.allowedKeys.includes(char)).join('')
: text;
const newText = this.prepareText(text);
const textAndCaret = new CaretString(newText, caretPosition, caretGravity);
const mask = this.pickMask(textAndCaret);
const result = mask.apply(textAndCaret);
Expand Down
4 changes: 3 additions & 1 deletion package/src/web/hooks/useMaskedTextInputListener/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const useMaskedTextInputListener = ({
affinityCalculationStrategy,
customNotations,
allowedKeys = '',
transformation = [],
autocomplete = true,
autoSkip = false,
isRTL = false,
Expand All @@ -41,7 +42,8 @@ const useMaskedTextInputListener = ({
autocomplete,
autoSkip,
isRTL,
allowedKeys
allowedKeys,
transformation
)
);

Expand Down
2 changes: 2 additions & 0 deletions package/src/web/views/MaskedTextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const MaskedTextInput = forwardRef<TextInput | null, MaskedTextInputProps>(
isRTL = false,
mask,
autoCapitalize = 'words',
transformation,
allowedKeys,
defaultValue,
onChange,
Expand All @@ -37,6 +38,7 @@ const MaskedTextInput = forwardRef<TextInput | null, MaskedTextInputProps>(
affinityCalculationStrategy,
customNotations,
allowedKeys,
transformation,
autocomplete,
autoSkip,
isRTL,
Expand Down
Loading