@v4fire/client
Version:
V4Fire client core library
402 lines (320 loc) • 9.47 kB
text/typescript
/*!
* 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
};