Comparing version 0.3.0 to 0.4.0
import { Validation, ValidationError, Validator } from "./validation"; | ||
type FieldValue<T> = (T extends number ? number : string) | undefined; | ||
/** | ||
* The value of a field is | ||
* 1. For literals and primitives: its primitive type, so `FieldValue<'foo'>` maps to `string`; | ||
* 2. For collections, such as arrays, sets or maps: a collection of `FieldValue` of the | ||
* collection's generic type, so `FieldValue<(1 | 2)[]>` maps to `number[]`; | ||
* 3. For other object types: the same type, so `FieldValue<Date>` is `Date`; | ||
* 4. For `unknown`: `any`; | ||
* 5. For anything else: `never`. | ||
*/ | ||
type FieldValue<T> = T extends string ? string : T extends number ? number : T extends bigint ? bigint : T extends boolean ? boolean : T extends undefined ? undefined : T extends (infer U)[] ? FieldValue<U>[] : T extends Set<infer U> ? Set<FieldValue<U>> : T extends Map<infer K, infer U> ? Map<K, FieldValue<U>> : T extends object ? T : T extends unknown ? any : never; | ||
/** | ||
* Utility type to check whether `A` is `never`, and if it's, fallback to `B` instead. | ||
@@ -9,24 +18,9 @@ * | ||
type Either<A, B> = [A] extends [never] ? B : A; | ||
/** | ||
* The field type as a literal string. | ||
*/ | ||
type FieldType = "number" | "text"; | ||
/** | ||
* Bag of options that a field of the specified `T` type can have. | ||
*/ | ||
type FieldOptions<T> = { | ||
validators: Validator<FieldValue<T>, T extends FieldValue<T> ? T : never>[]; | ||
initialValue?: FieldValue<T> | null; | ||
}; | ||
export declare abstract class Field<T> { | ||
protected readonly options: FieldOptions<T>; | ||
export declare class Field<T = unknown> { | ||
private readonly config; | ||
/** | ||
* The type of the field. | ||
*/ | ||
readonly type: FieldType; | ||
/** | ||
* Observable current value of the field. | ||
*/ | ||
value: FieldValue<T>; | ||
validation?: Validation<FieldValue<T>, T>; | ||
value: FieldValue<T> | undefined; | ||
validation?: Validation<FieldValue<T> | undefined, T>; | ||
/** | ||
@@ -40,12 +34,16 @@ * The first validation error of the field, if any. | ||
get errors(): readonly ValidationError[]; | ||
protected constructor(type: FieldType, options: FieldOptions<T>); | ||
private constructor(); | ||
/** | ||
* Creates a field instance which has a number type. It's optional by default. | ||
*/ | ||
static number(initialValue?: number | null): Field<number | undefined>; | ||
static number(initialValue?: number): Field<number | undefined>; | ||
/** | ||
* Creates a field instance which has a text type. It's optional by default. | ||
*/ | ||
static text(initialValue?: string | null): Field<string | undefined>; | ||
static text(initialValue?: string): Field<string | undefined>; | ||
/** | ||
* Creates a field instance whose type come from a validator. | ||
*/ | ||
static fromValidator<T>(validator: Validator<FieldValue<unknown>, T>): Field<T>; | ||
/** | ||
* Updates the underlying value of the field. | ||
@@ -60,20 +58,4 @@ * @returns self, for chaining | ||
*/ | ||
abstract addValidators<U extends T = T>(...validators: Validator<T, U>[]): Field<Either<U, T>>; | ||
/** | ||
* Builds and returns an observable bag of handy React props for rendering an input or textarea | ||
* element that represent this field. | ||
*/ | ||
getReactProps(): { | ||
type: string; | ||
value: NonNullable<FieldValue<T>> | ""; | ||
onChange: (evt: ChangeEvent) => void; | ||
}; | ||
/** | ||
* Callback for when a DOM ChangeEvent happens. | ||
*/ | ||
protected abstract onDOMChange(evt: ChangeEvent): void; | ||
addValidators<U extends T = T>(...validators: Validator<T, U>[]): Field<Either<U, T>>; | ||
} | ||
type ChangeEvent = { | ||
target: HTMLInputElement | HTMLTextAreaElement; | ||
}; | ||
export {}; |
@@ -13,8 +13,4 @@ "use strict"; | ||
class Field { | ||
options; | ||
config; | ||
/** | ||
* The type of the field. | ||
*/ | ||
type; | ||
/** | ||
* Observable current value of the field. | ||
@@ -36,7 +32,6 @@ */ | ||
} | ||
constructor(type, options) { | ||
constructor(config) { | ||
this.config = config; | ||
(0, mobx_1.makeObservable)(this); | ||
this.type = type; | ||
this.options = options; | ||
this.value = this.options?.initialValue ?? undefined; | ||
this.value = this.config.initialValue; | ||
} | ||
@@ -47,3 +42,3 @@ /** | ||
static number(initialValue) { | ||
return new NumberField({ initialValue, validators: [] }); | ||
return new Field({ initialValue, validators: [] }); | ||
} | ||
@@ -54,5 +49,11 @@ /** | ||
static text(initialValue) { | ||
return new TextField({ initialValue, validators: [] }); | ||
return new Field({ initialValue, validators: [] }); | ||
} | ||
/** | ||
* Creates a field instance whose type come from a validator. | ||
*/ | ||
static fromValidator(validator) { | ||
return new Field({ validators: [validator] }); | ||
} | ||
/** | ||
* Updates the underlying value of the field. | ||
@@ -67,3 +68,3 @@ * @returns self, for chaining | ||
async validate() { | ||
this.validation = (0, validation_1.createValidation)(this.options.validators); | ||
this.validation = (0, validation_1.createValidation)(this.config.validators); | ||
await this.validation.validate(this.value); | ||
@@ -73,14 +74,10 @@ return this.validation; | ||
/** | ||
* Builds and returns an observable bag of handy React props for rendering an input or textarea | ||
* element that represent this field. | ||
* Add one or more validators to this field. | ||
* @returns a new field with the new validator(s) appended to the previous list of validators. | ||
*/ | ||
getReactProps() { | ||
const { value, type } = this; | ||
return { | ||
type, | ||
// Default to empty string to avoid React complaining that an input | ||
// has changed from uncontrolled to controlled. | ||
value: value ?? "", | ||
onChange: (evt) => this.onDOMChange(evt), | ||
}; | ||
addValidators(...validators) { | ||
return new Field({ | ||
...this.config, | ||
validators: this.config.validators.concat(validators), | ||
}); | ||
} | ||
@@ -101,32 +98,1 @@ } | ||
], Field.prototype, "validate", null); | ||
class NumberField extends Field { | ||
constructor(options) { | ||
super("number", options); | ||
} | ||
onDOMChange(evt) { | ||
const value = Number(evt.target.value); | ||
this.set(isNaN(value) ? undefined : value); | ||
} | ||
/** @inheritdoc */ | ||
addValidators(...validators) { | ||
return new NumberField({ | ||
...this.options, | ||
validators: this.options.validators.concat(validators), | ||
}); | ||
} | ||
} | ||
class TextField extends Field { | ||
constructor(options) { | ||
super("text", options); | ||
} | ||
onDOMChange(evt) { | ||
this.set(evt.target.value); | ||
} | ||
/** @inheritdoc */ | ||
addValidators(...validators) { | ||
return new TextField({ | ||
...this.options, | ||
validators: this.options.validators.concat(validators), | ||
}); | ||
} | ||
} |
@@ -5,3 +5,3 @@ { | ||
"repository": "gustavohenke/fielded", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"license": "MIT", | ||
@@ -8,0 +8,0 @@ "homepage": "https://gustavohenke.github.io/fielded/", |
Sorry, the diff of this file is not supported yet
38821
632