feat(form): implement FormControl and ControlConfig prop triggerValidationOn
(#224)
* Added ValidationHooks and attribute to FormControl * Event listener uses data attribute to set type * Changed default event listener to blur * Add validation data attribute to other components * Adjusted querySelector to remove condition * Added nullish operator to prevent error * Added optional chaining to validator
This commit is contained in:
parent
4b5b81a2b5
commit
5b539c809c
8 changed files with 33 additions and 21 deletions
|
@ -1,4 +1,4 @@
|
||||||
import type { ValidatorRules } from "./validator.types";
|
import type { ValidationHooks, ValidatorRules } from "./validator.types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `ControlType` determines the type of form control
|
* `ControlType` determines the type of form control
|
||||||
|
@ -37,6 +37,7 @@ export interface ControlBase {
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
validators?: ValidatorRules;
|
validators?: ValidatorRules;
|
||||||
|
triggerValidationOn?: ValidationHooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Checkbox extends ControlBase {
|
export interface Checkbox extends ControlBase {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export type HookType = "onSubmit" | "onControlBlur" | "all";
|
export type HookType = "onSubmit" | "onControlBlur" | "all";
|
||||||
|
|
||||||
|
export type ValidationHooks = "" | "blur" | "keypress" | "click"; // More to be added
|
||||||
|
|
||||||
export type CategoryType = "error" | "warn" | "info";
|
export type CategoryType = "error" | "warn" | "info";
|
||||||
|
|
||||||
export type ValidatorRules =
|
export type ValidatorRules =
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type { Dropdown, ControlOption } from '@astro-reactive/common';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
control: Dropdown;
|
control: Dropdown;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { control, readOnly } = Astro.props;
|
const { control, readOnly } = Astro.props;
|
||||||
|
@ -26,22 +26,20 @@ const options = control.options.map((option: string | ControlOption) => {
|
||||||
name={control.name}
|
name={control.name}
|
||||||
id={control.id}
|
id={control.id}
|
||||||
disabled={readOnly || null}
|
disabled={readOnly || null}
|
||||||
|
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
control?.placeholder && (
|
control?.placeholder && (
|
||||||
<option value="" disabled selected={!control?.value}>
|
<option value="" disabled selected={!control?.value}>
|
||||||
{control.placeholder}
|
{control.placeholder}
|
||||||
</option>
|
</option>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
options.map((option: ControlOption) => (
|
options.map((option: ControlOption) => (
|
||||||
<option
|
<option value={option.value} selected={option.value === control.value}>
|
||||||
value={option.value}
|
{option.label}
|
||||||
selected={option.value === control.value}
|
</option>
|
||||||
>
|
))
|
||||||
{option.label}
|
}
|
||||||
</option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -46,6 +46,7 @@ const validatorAttributes: Record<string, string> = validators.reduce((prev, val
|
||||||
data-validator-error={hasError ? hasError.toString() : null}
|
data-validator-error={hasError ? hasError.toString() : null}
|
||||||
data-validator-warn={hasWarn ? hasWarn.toString() : null}
|
data-validator-warn={hasWarn ? hasWarn.toString() : null}
|
||||||
data-validator-info={hasInfo ? hasInfo.toString() : null}
|
data-validator-info={hasInfo ? hasInfo.toString() : null}
|
||||||
|
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
|
||||||
readonly={readOnly || null}
|
readonly={readOnly || null}
|
||||||
disabled={(readOnly || null) && control.type === 'checkbox'}
|
disabled={(readOnly || null) && control.type === 'checkbox'}
|
||||||
{...validatorAttributes}
|
{...validatorAttributes}
|
||||||
|
|
|
@ -33,6 +33,7 @@ const options = control.options.map((option: string | ControlOption) => {
|
||||||
checked={option.value === control.value}
|
checked={option.value === control.value}
|
||||||
readonly={readOnly || null}
|
readonly={readOnly || null}
|
||||||
disabled={readOnly || null}
|
disabled={readOnly || null}
|
||||||
|
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
|
||||||
/>
|
/>
|
||||||
<label for={control.id + '-' + index}>{option.label}</label>
|
<label for={control.id + '-' + index}>{option.label}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,6 +38,7 @@ const validatorAttributes: Record<string, string> = validators.reduce((prev, val
|
||||||
cols={control?.cols ?? 21}
|
cols={control?.cols ?? 21}
|
||||||
data-label={control?.label}
|
data-label={control?.label}
|
||||||
readonly={readOnly || null}
|
readonly={readOnly || null}
|
||||||
|
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
|
||||||
{...validatorAttributes}
|
{...validatorAttributes}
|
||||||
>
|
>
|
||||||
{control.value}
|
{control.value}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
TextArea,
|
TextArea,
|
||||||
ControlBase,
|
ControlBase,
|
||||||
ValidatorRules,
|
ValidatorRules,
|
||||||
|
ValidationHooks,
|
||||||
} from '@astro-reactive/common';
|
} from '@astro-reactive/common';
|
||||||
import ShortUniqueId from 'short-unique-id';
|
import ShortUniqueId from 'short-unique-id';
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ export class FormControl {
|
||||||
private _isPristine = true;
|
private _isPristine = true;
|
||||||
private _placeholder: string | null = null;
|
private _placeholder: string | null = null;
|
||||||
private _validators: ValidatorRules = [];
|
private _validators: ValidatorRules = [];
|
||||||
|
private _triggerValidationOn: ValidationHooks;
|
||||||
private _errors: ValidationError[] = [];
|
private _errors: ValidationError[] = [];
|
||||||
private _options: string[] | ControlOption[] = [];
|
private _options: string[] | ControlOption[] = [];
|
||||||
private _rows: number | null = null;
|
private _rows: number | null = null;
|
||||||
|
@ -47,6 +49,7 @@ export class FormControl {
|
||||||
label = '',
|
label = '',
|
||||||
placeholder = null,
|
placeholder = null,
|
||||||
validators = [],
|
validators = [],
|
||||||
|
triggerValidationOn = 'blur',
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 9 });
|
const uid = new ShortUniqueId({ length: 9 });
|
||||||
|
@ -57,6 +60,7 @@ export class FormControl {
|
||||||
this._label = label;
|
this._label = label;
|
||||||
this._placeholder = placeholder;
|
this._placeholder = placeholder;
|
||||||
this._validators = validators;
|
this._validators = validators;
|
||||||
|
this._triggerValidationOn = triggerValidationOn;
|
||||||
|
|
||||||
if (type === 'radio' || type === 'dropdown') {
|
if (type === 'radio' || type === 'dropdown') {
|
||||||
const { options = [] } = config as Radio | Dropdown;
|
const { options = [] } = config as Radio | Dropdown;
|
||||||
|
@ -111,6 +115,10 @@ export class FormControl {
|
||||||
return this._validators;
|
return this._validators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get triggerValidationOn() {
|
||||||
|
return this._triggerValidationOn;
|
||||||
|
}
|
||||||
|
|
||||||
get errors() {
|
get errors() {
|
||||||
return this._errors;
|
return this._errors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,10 @@ const { hook = 'all', displayErrorMessages = false } = Astro.props;
|
||||||
import { clearErrors, validate } from './core';
|
import { clearErrors, validate } from './core';
|
||||||
|
|
||||||
// const hook: HookType = (document.getElementById('hook') as HTMLInputElement).value as HookType;
|
// const hook: HookType = (document.getElementById('hook') as HTMLInputElement).value as HookType;
|
||||||
const inputs = [...document.querySelectorAll('form input')] as HTMLInputElement[];
|
const inputs = [...document.querySelectorAll('body *[data-validation-on]')] as HTMLInputElement[];
|
||||||
|
|
||||||
inputs?.forEach((input) => {
|
inputs?.forEach((input) => {
|
||||||
input.addEventListener('blur', (e: Event) => {
|
input.addEventListener(input.dataset?.validationOn ?? 'blur', (e: Event) => {
|
||||||
// NOTE: event target attribute names are converted to lowercase
|
// NOTE: event target attribute names are converted to lowercase
|
||||||
const element = e.target as HTMLInputElement;
|
const element = e.target as HTMLInputElement;
|
||||||
const attributeNames = element?.getAttributeNames() || [];
|
const attributeNames = element?.getAttributeNames() || [];
|
||||||
|
|
Loading…
Reference in a new issue