UNPKG

react-native-form-model

Version:

An easily testable and opinionated React Native form model builder written in pure JavaScript.

221 lines (206 loc) 7.16 kB
import { isNaN } from 'lodash'; import { KeyboardTypeOptions, TextInputProps } from 'react-native'; import { FieldModelOptions } from './FieldModel'; import InputFieldModel, { InputFieldModelOptions, ParsedInputFieldModelOptions, } from './InputFieldModel'; export interface KeyboardInputFieldModelBaseOptions extends Pick< TextInputProps, | 'clearTextOnFocus' | 'selectTextOnFocus' | 'clearButtonMode' | 'returnKeyType' > { optional?: boolean; mode?: 'plain' | 'contained'; } export interface KeyboardInputFieldModelOwnOptions extends KeyboardInputFieldModelBaseOptions, Pick< TextInputProps, 'textContentType' | 'autoCapitalize' | 'multiline' > { type: KeyboardInputFieldType; keyboardType: KeyboardTypeOptions; /** * If true, then the value will be updated as the user types. * If false (the default), the value will only update when * the user finishes typing. * * The latter is recommeded when a validator is used. */ submitOnChangeValue?: boolean; } export interface KeyboardInputFieldModelOptions<T> extends Partial<KeyboardInputFieldModelOwnOptions>, ParsedInputFieldModelOptions<T, string> {} export type KeyboardInputFieldType = | 'text' | 'secure' | 'email' | 'integer' | 'float' | 'unsignedInteger' | 'unsignedFloat'; export default class KeyboardInputFieldModel<T> extends InputFieldModel<T> implements KeyboardInputFieldModelOwnOptions { type: KeyboardInputFieldType; mode: KeyboardInputFieldModelOwnOptions['mode'] | undefined; keyboardType: KeyboardTypeOptions; returnKeyType: TextInputProps['returnKeyType']; textContentType: TextInputProps['textContentType'] | undefined; optional: boolean; multiline: boolean; autoFocus: boolean; clearTextOnFocus: TextInputProps['clearTextOnFocus']; selectTextOnFocus: TextInputProps['selectTextOnFocus']; clearButtonMode: TextInputProps['clearButtonMode']; autoCapitalize: TextInputProps['autoCapitalize'] | undefined; submitOnChangeValue: boolean; constructor(options: KeyboardInputFieldModelOptions<T>) { const optionsWithDefaults = { ...KeyboardInputFieldModel.defaults<T>(options), ...options, }; super(optionsWithDefaults); this.type = optionsWithDefaults.type; this.mode = optionsWithDefaults.mode; this.validation = optionsWithDefaults.validation; this.keyboardType = optionsWithDefaults.keyboardType; this.returnKeyType = optionsWithDefaults.returnKeyType; this.textContentType = optionsWithDefaults.textContentType; this.optional = optionsWithDefaults.optional; this.autoCapitalize = optionsWithDefaults.autoCapitalize; const { multiline = false, autoFocus = false, clearTextOnFocus = false, selectTextOnFocus = false, clearButtonMode = 'never', submitOnChangeValue = false, } = optionsWithDefaults; this.multiline = multiline; this.autoFocus = autoFocus; this.clearTextOnFocus = clearTextOnFocus; this.selectTextOnFocus = selectTextOnFocus; this.clearButtonMode = clearButtonMode; this.submitOnChangeValue = submitOnChangeValue; } static defaults<T>( options: KeyboardInputFieldModelOptions<T> ): KeyboardInputFieldModelDefaults<T> { return _defaults(options); } } export type KeyboardInputFieldModelDefaults<T> = Omit< KeyboardInputFieldModelOwnOptions & InputFieldModelOptions<T, string>, keyof FieldModelOptions | 'value' | 'defaultValue' > & Required<Pick<KeyboardInputFieldModelOwnOptions, 'optional'>>; let _defaults = ( options: KeyboardInputFieldModelOptions<any> ): KeyboardInputFieldModelDefaults<any> => { const { type = 'text', mode = 'plain', optional = false } = options; const sharedDefaults = { type, mode, optional, }; switch (type) { case 'text': return { ...sharedDefaults, parseInput: x => x === null || typeof x === 'undefined' ? '' : String(x), keyboardType: 'default', }; case 'secure': return { ...sharedDefaults, parseInput: x => x === null || typeof x === 'undefined' ? '' : String(x), keyboardType: 'default', textContentType: 'password', }; case 'email': return { ...sharedDefaults, parseInput: x => x === null || typeof x === 'undefined' ? '' : String(x), keyboardType: 'email-address', textContentType: 'emailAddress', autoCapitalize: 'none', }; case 'integer': return { ...sharedDefaults, parseInput: parseInt, validation: createDefaultNumberValidator({ optional }), keyboardType: 'numbers-and-punctuation', }; case 'float': return { ...sharedDefaults, parseInput: optional ? x => (!x ? undefined : parseFloat(x)) : parseFloat, validation: createDefaultNumberValidator({ optional }), keyboardType: 'numbers-and-punctuation', }; case 'unsignedInteger': return { ...sharedDefaults, parseInput: parseInt, validation: createDefaultNumberValidator({ optional, positiveOnly: true, }), keyboardType: 'number-pad', }; case 'unsignedFloat': return { ...sharedDefaults, parseInput: optional ? x => (!x ? undefined : parseFloat(x)) : parseFloat, validation: createDefaultNumberValidator({ optional, positiveOnly: true, }), keyboardType: 'decimal-pad', }; } throw new Error('Invalid input type: ' + type); }; function createDefaultNumberValidator({ optional = false, positiveOnly = false, }: { optional?: boolean; positiveOnly?: boolean; }) { if (optional) { return (x: number) => ({ valid: typeof x === 'undefined' || String(x) === '' || x === null || (typeof x === 'number' && !isNaN(x) && isFinite(x) && (!positiveOnly || x >= 0)), }); } else { return (x: number) => ({ valid: typeof x === 'number' && !isNaN(x) && isFinite(x) && (!positiveOnly || x >= 0), }); } }