UNPKG

@v4fire/client

Version:

V4Fire client core library

402 lines (320 loc) • 9.47 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ import type bInput from 'form/b-input/b-input'; import type iInput from 'super/i-input/i-input'; import type { ValidatorsDecl, ValidatorParams, ValidatorResult } from 'super/i-input/i-input'; import type { NumberValidatorParams, NumberValidatorResult, DateValidatorParams, DateValidatorResult, PasswordValidatorParams, PasswordValidatorResult } from 'form/b-input/modules/validators/interface'; export * from 'form/b-input/modules/validators/interface'; export default <ValidatorsDecl<bInput>>{ //#if runtime has bInput/validators /** * Checks that a component value must be matched as a number * * @param msg * @param type * @param min * @param max * @param precision * @param strictPrecision * @param separator * @param styleSeparator * @param showMsg */ async number({ msg, type, min, max, precision, strictPrecision, separator = ['.', ','], styleSeparator = [' ', '_'], showMsg = true }: NumberValidatorParams): Promise<ValidatorResult<NumberValidatorResult>> { const numStyleRgxp = new RegExp(`[${Array.concat([], styleSeparator).join('')}]`, 'g'), sepStyleRgxp = new RegExp(`[${Array.concat([], separator).join('')}]`); const value = String((await this.formValue) ?? '') .replace(numStyleRgxp, '') .replace(sepStyleRgxp, '.'); if (value === '') { return true; } if (precision != null && !Number.isNatural(precision)) { throw new TypeError('The precision value can be defined only as a natural number'); } const error = ( defMsg = this.t`The value is not a number`, errorValue: string | number = value, errorType: NumberValidatorResult['name'] = 'INVALID_VALUE' ) => { const err = <NumberValidatorResult>{ name: errorType, value: errorValue, // Skip undefined values params: Object.mixin(false, {}, { type, min, max, precision, strictPrecision, separator, styleSeparator }) }; this.setValidationMsg(this.getValidatorMsg(err, msg, defMsg), showMsg); return <ValidatorResult<NumberValidatorResult>>err; }; if (!/^-?\d*(?:\.\d*|$)/.test(value)) { return error(); } const numValue = parseFloat(value); switch (type) { case 'uint': if (!Number.isNonNegative(numValue) || !Number.isInteger(numValue)) { return error(this.t`The value does not match with an unsigned integer type`, numValue); } break; case 'int': if (!Number.isInteger(numValue)) { return error(this.t`The value does not match with an integer type`, numValue); } break; case 'ufloat': if (!Number.isNonNegative(numValue)) { return error(this.t`The value does not match with an unsigned float type`, numValue); } break; default: // Do nothing } if (precision != null) { const chunks = value.split('.', 2); if (strictPrecision) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (chunks[1] == null || chunks[1].length !== precision) { return error(this.t('A decimal part should have {precision} digits', {precision}), numValue, 'DECIMAL_LENGTH'); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (chunks[1] != null && chunks[1].length > precision) { return error(this.t('A decimal part should have no more than {precision} digits', {precision}), numValue, 'DECIMAL_LENGTH'); } } if (min != null && numValue < min) { return error(this.t('A value must be at least {min}', {min}), numValue, 'MIN'); } if (max != null && numValue > max) { return error(this.t('A value must be no more than {max}', {max}), numValue, 'MAX'); } return true; }, /** * Checks that a component value must be matched as a date * * @param msg * @param past * @param future * @param min * @param max * @param showMsg */ async date({ msg, past, future, min, max, showMsg = true }: DateValidatorParams): Promise<ValidatorResult<DateValidatorResult>> { const value = await this.formValue; if (value === undefined || Object.isString(value) && value.trim() === '') { return true; } const dateValue = Date.create(isNaN(Object.cast(value)) ? value : Number(value)); const error = ( type: DateValidatorResult['name'] = 'INVALID_VALUE', defMsg = this.t`The value can't be parsed as a date`, errorValue: Date | string = dateValue ) => { const err = <DateValidatorResult>{ name: type, value: errorValue, // Skip undefined values params: Object.mixin(false, {}, {past, future, min, max}) }; this.setValidationMsg(this.getValidatorMsg(err, msg, defMsg), showMsg); return <ValidatorResult<DateValidatorResult>>err; }; if (isNaN(dateValue.valueOf())) { return error(undefined, undefined, value); } const isPast = dateValue.isPast(), isFuture = dateValue.isFuture(); if (past && !isPast) { return error('NOT_PAST', this.t`A date value must be in the past`); } if (past === false && isPast) { return error('IS_PAST', this.t`A date value can't be in the past`); } if (future && !isFuture) { return error('NOT_FUTURE', this.t`A date value must be in the future`); } if (future === false && isFuture) { return error('IS_FUTURE', this.t`A date value can't be in the future`); } if (min != null && !dateValue.isAfter(min, 1)) { return error('MIN', this.t('A date value must be at least "{date}"', {date: new Date(min).toDateString()})); } if (max != null && !dateValue.isBefore(max, 1)) { return error('MAX', this.t('A date value must be no more than "{date}"', {date: new Date(max).toDateString()})); } return true; }, /** * Checks that a component value must be matched as an email string * * @param msg * @param showMsg */ async email({msg, showMsg = true}: ValidatorParams): Promise<ValidatorResult<boolean>> { const value = String((await this.formValue) ?? ''); if (value === '') { return true; } if (!/.+@.+/.test(value)) { this.setValidationMsg(this.getValidatorMsg(false, msg, this.t`Invalid email format`), showMsg); return false; } return true; }, /** * Checks that a component value must be matched as a password * * @param msg * @param pattern * @param min * @param max * @param confirmComponent * @param oldPassComponent * @param skipLength * @param showMsg */ async password({ msg, pattern = /^\w*$/, min = 6, max = 18, confirmComponent, oldPassComponent, skipLength, showMsg = true }: PasswordValidatorParams): Promise<ValidatorResult> { const value = String((await this.formValue) ?? ''); if (value === '') { return true; } const error = ( type: PasswordValidatorResult['name'] = 'NOT_MATCH', defMsg = this.t`A password must contain only allowed characters`, errorValue: string | number | [string, string] = value ) => { const err = <PasswordValidatorResult>{ name: type, value: errorValue, // Skip undefined values params: Object.mixin(false, {}, { pattern, min, max, confirmComponent, oldPassComponent, skipLength }) }; this.setValidationMsg(this.getValidatorMsg(err, msg, defMsg), showMsg); return <ValidatorResult<PasswordValidatorResult>>err; }; let rgxp: CanUndef<RegExp>; if (Object.isString(pattern)) { rgxp = new RegExp(pattern); } else if (Object.isRegExp(pattern)) { rgxp = pattern; } if (rgxp == null) { throw new ReferenceError('A password pattern is not defined'); } if (!rgxp.test(value)) { return error(); } if (!skipLength) { const {length} = [...value.letters()]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (min != null && length < min) { return error('MIN', this.t('Password length must be at least {min} characters', {min})); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (max != null && length > max) { return error('MAX', this.t('Password length must be no more than {max} characters', {max})); } } const {dom} = this.unsafe; if (oldPassComponent != null) { const connectedInput = dom.getComponent<iInput>(oldPassComponent); if (connectedInput == null) { throw new ReferenceError(`Can't find a component by the provided selector "${oldPassComponent}"`); } const connectedValue = String(await connectedInput.formValue ?? ''); if (connectedValue !== '') { if (connectedValue === value) { return error('OLD_IS_NEW', this.t`The old and new password are the same`); } void connectedInput.setMod('valid', true); } } if (confirmComponent != null) { const connectedInput = dom.getComponent<iInput>(confirmComponent); if (connectedInput == null) { throw new ReferenceError(`Can't find a component by the provided selector "${confirmComponent}"`); } const connectedValue = String(await connectedInput.formValue ?? ''); if (connectedValue !== '') { if (connectedValue !== value) { return error('NOT_CONFIRM', this.t`The passwords aren't match`, [value, String(connectedValue)]); } void connectedInput.setMod('valid', true); } } return true; } //#endif };