feat: Components conststructors are exposed as external API, added TypeScript declarations
This commit is contained in:
parent
6224b40e02
commit
dfc6557bc6
35
.eslintrc.js
35
.eslintrc.js
@ -1,30 +1,27 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
plugins: ['@typescript-eslint'],
|
||||
|
||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||
extends: [
|
||||
'standard',
|
||||
'@vue/standard',
|
||||
'@vue/typescript',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/recommended',
|
||||
],
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'vue',
|
||||
],
|
||||
|
||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/recommended',
|
||||
],
|
||||
|
||||
rules: {
|
||||
'@typescript-eslint/camelcase': ['error', {
|
||||
allow: ['^__Formulario'],
|
||||
}],
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off', // @TODO
|
||||
'@typescript-eslint/no-unused-vars': ['error'], // @TODO
|
||||
|
@ -9,7 +9,6 @@ export default {
|
||||
input: 'src/index.ts',
|
||||
output: [{
|
||||
name: 'Formulario',
|
||||
exports: 'default',
|
||||
globals: {
|
||||
'is-plain-object': 'isPlainObject',
|
||||
'is-url': 'isUrl',
|
||||
|
@ -11,7 +11,6 @@ export default {
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
name: 'VueFormulario',
|
||||
exports: 'default',
|
||||
format: 'iife',
|
||||
globals: {
|
||||
'is-plain-object': 'isPlainObject',
|
||||
|
94
index.d.ts
vendored
Normal file
94
index.d.ts
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
import type { Vue } from 'vue/types/vue'
|
||||
import type { PluginObject } from 'vue/types/plugin'
|
||||
|
||||
import type { DefineComponent } from './types/vue'
|
||||
|
||||
import type { FormularioFormConstructor } from './types/form'
|
||||
|
||||
import type {
|
||||
ModelGetConverter,
|
||||
ModelSetConverter,
|
||||
ValidationBehaviour,
|
||||
UnregisterBehaviour,
|
||||
} from './types/field'
|
||||
|
||||
import type {
|
||||
ValidationMessageFn,
|
||||
ValidationMessageI18NFn,
|
||||
ValidationRuleFn,
|
||||
Violation,
|
||||
} from './types/validation'
|
||||
|
||||
import type { Options } from './types/plugin'
|
||||
|
||||
declare const FormularioForm: FormularioFormConstructor
|
||||
|
||||
export { FormularioForm }
|
||||
|
||||
declare const FormularioField: DefineComponent<{
|
||||
name: string;
|
||||
value?: unknown;
|
||||
validation?: string|any[];
|
||||
/** Defaults to 'demand' */
|
||||
validationBehavior?: ValidationBehaviour;
|
||||
validationRules?: Record<string, ValidationRuleFn>;
|
||||
validationMessages?: Record<string, ValidationMessageI18NFn|string>;
|
||||
errorsDisabled?: boolean;
|
||||
modelGetConverter?: ModelGetConverter;
|
||||
modelSetConverter?: ModelSetConverter;
|
||||
/** Defaults to 'none' */
|
||||
unregisterBehavior?: UnregisterBehaviour;
|
||||
tag?: string;
|
||||
}, {
|
||||
runValidation(): Promise<Violation[]>;
|
||||
hasValidationErrors (): Promise<boolean>;
|
||||
/** @internal */
|
||||
resetValidation(): void;
|
||||
}>
|
||||
|
||||
export { FormularioField }
|
||||
|
||||
declare class Formulario {
|
||||
public validationRules: Record<string, ValidationRuleFn>;
|
||||
public validationMessages: Record<string, ValidationMessageI18NFn|string>;
|
||||
|
||||
constructor (options?: Options);
|
||||
|
||||
/** Given a set of options, apply them to the pre-existing options. */
|
||||
public extend (extendWith: Options): Formulario;
|
||||
|
||||
public runValidation (id: string): Promise<Record<string, Violation[]>>;
|
||||
|
||||
public resetValidation (id: string): void;
|
||||
|
||||
/** Used by forms instances to add themselves into a registry */
|
||||
public register (id: string, form: InstanceType<FormularioFormConstructor>): void;
|
||||
|
||||
/** Used by forms instances to remove themselves from a registry */
|
||||
public unregister (id: string): void;
|
||||
|
||||
/** Get validation rules by merging any passed in with global rules. */
|
||||
public getRules (extendWith: Record<string, ValidationRuleFn> = {}): Record<string, ValidationRuleFn>;
|
||||
|
||||
/** Get validation messages by merging any passed in with global messages. */
|
||||
public getMessages (
|
||||
vm: Vue,
|
||||
extendWith: Record<string, ValidationMessageI18NFn|string>
|
||||
): Record<string, ValidationMessageFn>;
|
||||
}
|
||||
|
||||
export { Formulario }
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
readonly $formulario: Formulario;
|
||||
}
|
||||
}
|
||||
|
||||
declare const VueFormulario: PluginObject<Options> & {
|
||||
Formulario: Formulario,
|
||||
}
|
||||
|
||||
export default VueFormulario
|
26
package.json
26
package.json
@ -24,7 +24,7 @@
|
||||
"build:iife": "rollup --config build/rollup.iife.config.js --format iife --file dist/formulario.min.js",
|
||||
"build:size": "gzip -c dist/formulario.esm.js | wc -c",
|
||||
"build:umd": "rollup --config build/rollup.config.js --format umd --file dist/formulario.umd.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"lint": "eslint --ext .js,.mjs,.ts,.vue",
|
||||
"release": "standard-version",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
@ -52,28 +52,24 @@
|
||||
"@storybook/vue": "^6.0.26",
|
||||
"@types/is-url": "^1.2.28",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||
"@typescript-eslint/parser": "^6.16.0",
|
||||
"@vue/cli-plugin-babel": "^4.3.1",
|
||||
"@vue/cli-plugin-eslint": "^4.3.1",
|
||||
"@vue/cli-plugin-typescript": "^4.5.7",
|
||||
"@vue/cli-service": "^4.5.4",
|
||||
"@vue/component-compiler-utils": "^3.1.2",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"@vue/test-utils": "^1.0.2",
|
||||
"autoprefixer": "^9.7.6",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^25.5.1",
|
||||
"bootstrap-scss": "^4.5.2",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"flush-promises": "^1.0.2",
|
||||
"jest": "^26.5.2",
|
||||
"jest-vue-preprocessor": "^1.7.1",
|
||||
@ -88,7 +84,7 @@
|
||||
"sass-loader": "^10.0.3",
|
||||
"standard-version": "^9.3.0",
|
||||
"ts-jest": "^26.4.1",
|
||||
"typescript": "~3.9.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-cli-plugin-storybook": "^1.3.0",
|
||||
"vue-jest": "^3.0.5",
|
||||
|
@ -1,33 +1,28 @@
|
||||
import merge from '@/utils/merge'
|
||||
import validationRules from '@/validation/rules'
|
||||
import validationMessages from '@/validation/messages'
|
||||
import type { FormularioFormConstructor } from '../types/form'
|
||||
|
||||
import {
|
||||
import type {
|
||||
ValidationContext,
|
||||
ValidationRuleFn,
|
||||
ValidationMessageFn,
|
||||
ValidationMessageI18NFn,
|
||||
Violation,
|
||||
} from '@/validation/validator'
|
||||
} from '../types/validation'
|
||||
|
||||
import { FormularioForm } from '@/types'
|
||||
import type { Options } from '../types/plugin'
|
||||
|
||||
export interface FormularioOptions {
|
||||
validationRules?: Record<string, ValidationRuleFn>;
|
||||
validationMessages?: Record<string, ValidationMessageI18NFn|string>;
|
||||
}
|
||||
import merge from '@/utils/merge'
|
||||
|
||||
import validationRules from '@/validation/rules'
|
||||
import validationMessages from '@/validation/messages'
|
||||
|
||||
/**
|
||||
* The base formulario library.
|
||||
*/
|
||||
export default class Formulario {
|
||||
public validationRules: Record<string, ValidationRuleFn> = {}
|
||||
public validationMessages: Record<string, ValidationMessageI18NFn|string> = {}
|
||||
|
||||
private readonly registry: Map<string, FormularioForm>
|
||||
private readonly _registry: Map<string, InstanceType<FormularioFormConstructor>>
|
||||
|
||||
public constructor (options?: FormularioOptions) {
|
||||
this.registry = new Map()
|
||||
public constructor (options?: Options) {
|
||||
this._registry = new Map()
|
||||
|
||||
this.validationRules = validationRules
|
||||
this.validationMessages = validationMessages
|
||||
@ -38,7 +33,7 @@ export default class Formulario {
|
||||
/**
|
||||
* Given a set of options, apply them to the pre-existing options.
|
||||
*/
|
||||
public extend (extendWith: FormularioOptions): Formulario {
|
||||
public extend (extendWith: Options): Formulario {
|
||||
if (typeof extendWith === 'object') {
|
||||
this.validationRules = merge(this.validationRules, extendWith.validationRules || {})
|
||||
this.validationMessages = merge(this.validationMessages, extendWith.validationMessages || {})
|
||||
@ -48,21 +43,21 @@ export default class Formulario {
|
||||
}
|
||||
|
||||
public runValidation (id: string): Promise<Record<string, Violation[]>> {
|
||||
if (!this.registry.has(id)) {
|
||||
if (!this._registry.has(id)) {
|
||||
throw new Error(`[Formulario]: Formulario.runValidation(): no forms with id "${id}"`)
|
||||
}
|
||||
|
||||
const form = this.registry.get(id) as FormularioForm
|
||||
const form = this._registry.get(id) as InstanceType<FormularioFormConstructor>
|
||||
|
||||
return form.runValidation()
|
||||
}
|
||||
|
||||
public resetValidation (id: string): void {
|
||||
if (!this.registry.has(id)) {
|
||||
if (!this._registry.has(id)) {
|
||||
return
|
||||
}
|
||||
|
||||
const form = this.registry.get(id) as FormularioForm
|
||||
const form = this._registry.get(id) as InstanceType<FormularioFormConstructor>
|
||||
|
||||
form.resetValidation()
|
||||
}
|
||||
@ -71,12 +66,12 @@ export default class Formulario {
|
||||
* Used by forms instances to add themselves into a registry
|
||||
* @internal
|
||||
*/
|
||||
public register (id: string, form: FormularioForm): void {
|
||||
if (this.registry.has(id)) {
|
||||
public register (id: string, form: InstanceType<FormularioFormConstructor>): void {
|
||||
if (this._registry.has(id)) {
|
||||
throw new Error(`[Formulario]: Formulario.register(): id "${id}" is already in use`)
|
||||
}
|
||||
|
||||
this.registry.set(id, form)
|
||||
this._registry.set(id, form)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,8 +79,8 @@ export default class Formulario {
|
||||
* @internal
|
||||
*/
|
||||
public unregister (id: string): void {
|
||||
if (this.registry.has(id)) {
|
||||
this.registry.delete(id)
|
||||
if (this._registry.has(id)) {
|
||||
this._registry.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type {
|
||||
Context,
|
||||
Empty,
|
||||
ModelGetConverter,
|
||||
ModelSetConverter,
|
||||
ValidationBehaviour,
|
||||
UnregisterBehaviour,
|
||||
} from '../types/field'
|
||||
|
||||
import type {
|
||||
ValidationRuleFn,
|
||||
ValidationMessageI18NFn,
|
||||
Violation,
|
||||
} from '../types/validation'
|
||||
|
||||
import Vue from 'vue'
|
||||
import {
|
||||
Component,
|
||||
@ -16,41 +31,22 @@ import {
|
||||
Prop,
|
||||
Watch,
|
||||
} from 'vue-property-decorator'
|
||||
|
||||
import { processConstraints, validate } from '@/validation/validator'
|
||||
|
||||
import { deepEquals, has, snakeToCamel } from './utils'
|
||||
import {
|
||||
processConstraints,
|
||||
validate,
|
||||
ValidationRuleFn,
|
||||
ValidationMessageI18NFn,
|
||||
Violation,
|
||||
} from '@/validation/validator'
|
||||
|
||||
import {
|
||||
FormularioFieldContext,
|
||||
FormularioFieldModelGetConverter as ModelGetConverter,
|
||||
FormularioFieldModelSetConverter as ModelSetConverter,
|
||||
Empty,
|
||||
} from '@/types'
|
||||
|
||||
import { UNREGISTER_BEHAVIOR } from '@/enum'
|
||||
|
||||
const VALIDATION_BEHAVIOR = {
|
||||
DEMAND: 'demand',
|
||||
LIVE: 'live',
|
||||
SUBMIT: 'submit',
|
||||
}
|
||||
|
||||
@Component({ name: 'FormularioField', inheritAttrs: false })
|
||||
export default class FormularioField extends Vue {
|
||||
@Inject({ default: '' }) __Formulario_path!: string
|
||||
@Inject({ default: undefined }) __FormularioForm_set!: Function|undefined
|
||||
@Inject({ default: () => (): void => {} }) __FormularioForm_emitInput!: Function
|
||||
@Inject({ default: () => (): void => {} }) __FormularioForm_emitValidation!: Function
|
||||
@Inject({ default: undefined }) __FormularioForm_register!: Function|undefined
|
||||
@Inject({ default: undefined }) __FormularioForm_unregister!: Function|undefined
|
||||
@Inject({ default: undefined }) __FormularioForm_set!: ((path: string, value: unknown) => void)|undefined
|
||||
@Inject({ default: () => (): void => {} }) __FormularioForm_emitInput!: () => void
|
||||
@Inject({ default: () => (): void => {} }) __FormularioForm_emitValidation!: (path: string, violations: Violation[]) => void
|
||||
@Inject({ default: undefined }) __FormularioForm_register!: ((path: string, field: FormularioField) => void)|undefined
|
||||
@Inject({ default: undefined }) __FormularioForm_unregister!: ((path: string, behavior: UnregisterBehaviour) => void)|undefined
|
||||
|
||||
@Inject({ default: () => (): Record<string, unknown> => ({}) })
|
||||
__FormularioForm_getState!: () => Record<string, unknown>
|
||||
__FormularioForm_getState!: () => Record<string, unknown>
|
||||
|
||||
@Model('input', { default: '' }) value!: unknown
|
||||
|
||||
@ -63,9 +59,9 @@ export default class FormularioField extends Vue {
|
||||
@Prop({ default: () => ({}) }) validationRules!: Record<string, ValidationRuleFn>
|
||||
@Prop({ default: () => ({}) }) validationMessages!: Record<string, ValidationMessageI18NFn|string>
|
||||
@Prop({
|
||||
default: VALIDATION_BEHAVIOR.DEMAND,
|
||||
validator: behavior => Object.values(VALIDATION_BEHAVIOR).includes(behavior)
|
||||
}) validationBehavior!: string
|
||||
default: 'demand',
|
||||
validator: (behavior: string) => ['demand', 'live', 'submit'].includes(behavior)
|
||||
}) validationBehavior!: ValidationBehaviour
|
||||
|
||||
// Affects only setting of local errors
|
||||
@Prop({ default: false }) errorsDisabled!: boolean
|
||||
@ -74,7 +70,7 @@ export default class FormularioField extends Vue {
|
||||
@Prop({ default: () => <T, U>(value: U|T): U|T => value }) modelSetConverter!: ModelSetConverter
|
||||
|
||||
@Prop({ default: 'div' }) tag!: string
|
||||
@Prop({ default: UNREGISTER_BEHAVIOR.NONE }) unregisterBehavior!: string
|
||||
@Prop({ default: 'none' }) unregisterBehavior!: UnregisterBehaviour
|
||||
|
||||
public proxy: unknown = this.hasModel ? this.value : ''
|
||||
|
||||
@ -88,14 +84,12 @@ export default class FormularioField extends Vue {
|
||||
return this.__Formulario_path !== '' ? `${this.__Formulario_path}.${this.name}` : this.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this formulario element is v-modeled or not.
|
||||
*/
|
||||
/** Determines if this formulario element is v-modeled or not. */
|
||||
public get hasModel (): boolean {
|
||||
return has(this.$options.propsData || {}, 'value')
|
||||
}
|
||||
|
||||
private get context (): FormularioFieldContext<unknown> {
|
||||
private get context (): Context<unknown> {
|
||||
return Object.defineProperty({
|
||||
name: this.fullPath,
|
||||
path: this.fullPath,
|
||||
@ -103,15 +97,15 @@ export default class FormularioField extends Vue {
|
||||
violations: this.violations,
|
||||
errors: this.localErrors,
|
||||
allErrors: [...this.localErrors, ...this.violations.map(v => v.message)],
|
||||
}, 'model', {
|
||||
} as Context<unknown>, 'model', {
|
||||
get: () => this.modelGetConverter(this.proxy),
|
||||
set: (value: unknown): void => {
|
||||
this.syncProxy(this.modelSetConverter(value, this.proxy))
|
||||
this._syncProxy(this.modelSetConverter(value, this.proxy))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private get normalizedValidationRules (): Record<string, ValidationRuleFn> {
|
||||
private get _normalizedValidationRules (): Record<string, ValidationRuleFn> {
|
||||
const rules: Record<string, ValidationRuleFn> = {}
|
||||
Object.keys(this.validationRules).forEach(key => {
|
||||
rules[snakeToCamel(key)] = this.validationRules[key]
|
||||
@ -119,7 +113,7 @@ export default class FormularioField extends Vue {
|
||||
return rules
|
||||
}
|
||||
|
||||
private get normalizedValidationMessages (): Record<string, ValidationMessageI18NFn|string> {
|
||||
private get _normalizedValidationMessages (): Record<string, ValidationMessageI18NFn|string> {
|
||||
const messages: Record<string, ValidationMessageI18NFn|string> = {}
|
||||
Object.keys(this.validationMessages).forEach(key => {
|
||||
messages[snakeToCamel(key)] = this.validationMessages[key]
|
||||
@ -128,42 +122,70 @@ export default class FormularioField extends Vue {
|
||||
}
|
||||
|
||||
@Watch('value')
|
||||
private onValueChange (): void {
|
||||
this.syncProxy(this.value)
|
||||
private _onValueChange (): void {
|
||||
this._syncProxy(this.value)
|
||||
}
|
||||
|
||||
@Watch('proxy')
|
||||
private onProxyChange (): void {
|
||||
if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
||||
private _onProxyChange (): void {
|
||||
if (this.validationBehavior === 'live') {
|
||||
this.runValidation()
|
||||
} else {
|
||||
this.resetValidation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
public created (): void {
|
||||
if (typeof this.__FormularioForm_register === 'function') {
|
||||
this.__FormularioForm_register(this.fullPath, this)
|
||||
}
|
||||
|
||||
if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
||||
if (this.validationBehavior === 'live') {
|
||||
this.runValidation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @internal */
|
||||
public beforeDestroy (): void {
|
||||
if (typeof this.__FormularioForm_unregister === 'function') {
|
||||
this.__FormularioForm_unregister(this.fullPath, this.unregisterBehavior)
|
||||
}
|
||||
}
|
||||
|
||||
private syncProxy (value: unknown): void {
|
||||
public runValidation (): Promise<Violation[]> {
|
||||
this.validationRun = this._validate().then(violations => {
|
||||
this.violations = violations
|
||||
this._emitValidation(this.fullPath, violations)
|
||||
|
||||
return this.violations
|
||||
})
|
||||
|
||||
return this.validationRun
|
||||
}
|
||||
|
||||
public hasValidationErrors (): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
this.$nextTick(() => {
|
||||
this.validationRun.then(() => resolve(this.violations.length > 0))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public setErrors (errors: string[]): void {
|
||||
if (!this.errorsDisabled) {
|
||||
this.localErrors = errors
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public resetValidation (): void {
|
||||
this.localErrors = []
|
||||
this.violations = []
|
||||
}
|
||||
|
||||
private _syncProxy (value: unknown): void {
|
||||
if (!deepEquals(value, this.proxy)) {
|
||||
this.proxy = value
|
||||
this.$emit('input', value)
|
||||
@ -175,22 +197,11 @@ export default class FormularioField extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
public runValidation (): Promise<Violation[]> {
|
||||
this.validationRun = this.validate().then(violations => {
|
||||
this.violations = violations
|
||||
this.emitValidation(this.fullPath, violations)
|
||||
|
||||
return this.violations
|
||||
})
|
||||
|
||||
return this.validationRun
|
||||
}
|
||||
|
||||
private validate (): Promise<Violation[]> {
|
||||
private _validate (): Promise<Violation[]> {
|
||||
return validate(processConstraints(
|
||||
this.validation,
|
||||
this.$formulario.getRules(this.normalizedValidationRules),
|
||||
this.$formulario.getMessages(this, this.normalizedValidationMessages),
|
||||
this.$formulario.getRules(this._normalizedValidationRules),
|
||||
this.$formulario.getMessages(this, this._normalizedValidationMessages),
|
||||
), {
|
||||
value: this.proxy,
|
||||
name: this.fullPath,
|
||||
@ -198,36 +209,11 @@ export default class FormularioField extends Vue {
|
||||
})
|
||||
}
|
||||
|
||||
private emitValidation (path: string, violations: Violation[]): void {
|
||||
private _emitValidation (path: string, violations: Violation[]): void {
|
||||
this.$emit('validation', { path, violations })
|
||||
if (typeof this.__FormularioForm_emitValidation === 'function') {
|
||||
this.__FormularioForm_emitValidation(path, violations)
|
||||
}
|
||||
}
|
||||
|
||||
public hasValidationErrors (): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
this.$nextTick(() => {
|
||||
this.validationRun.then(() => resolve(this.violations.length > 0))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public setErrors (errors: string[]): void {
|
||||
if (!this.errorsDisabled) {
|
||||
this.localErrors = errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public resetValidation (): void {
|
||||
this.localErrors = []
|
||||
this.violations = []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -5,7 +5,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { UnregisterBehaviour } from '../types/field'
|
||||
import type { Violation } from '../types/validation'
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
import {
|
||||
Component,
|
||||
Model,
|
||||
@ -13,6 +17,7 @@ import {
|
||||
Provide,
|
||||
Watch,
|
||||
} from 'vue-property-decorator'
|
||||
|
||||
import {
|
||||
id,
|
||||
clone,
|
||||
@ -25,9 +30,6 @@ import {
|
||||
} from '@/utils'
|
||||
|
||||
import { FormularioField } from '@/types'
|
||||
import { Violation } from '@/validation/validator'
|
||||
|
||||
import { UNREGISTER_BEHAVIOR } from '@/enum'
|
||||
|
||||
const update = (state: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> => {
|
||||
if (value === undefined) {
|
||||
@ -90,11 +92,11 @@ export default class FormularioForm extends Vue {
|
||||
}
|
||||
|
||||
@Provide('__FormularioForm_unregister')
|
||||
private unregister (path: string, behavior: string): void {
|
||||
private unregister (path: string, behavior: UnregisterBehaviour): void {
|
||||
if (this.registry.has(path)) {
|
||||
this.registry.delete(path)
|
||||
|
||||
if (behavior === UNREGISTER_BEHAVIOR.UNSET) {
|
||||
if (behavior === 'unset') {
|
||||
this.proxy = unset(this.proxy, path) as Record<string, unknown>
|
||||
this.emitInput()
|
||||
}
|
||||
@ -183,17 +185,17 @@ export default class FormularioForm extends Vue {
|
||||
})
|
||||
}
|
||||
|
||||
public setErrors ({ fieldsErrors, formErrors }: {
|
||||
fieldsErrors?: Record<string, string[]>;
|
||||
public setErrors ({ formErrors, fieldsErrors }: {
|
||||
formErrors?: string[];
|
||||
fieldsErrors?: Record<string, string[]>;
|
||||
}): void {
|
||||
this.localFieldsErrors = fieldsErrors || {}
|
||||
this.localFormErrors = formErrors || []
|
||||
this.localFieldsErrors = fieldsErrors || {}
|
||||
}
|
||||
|
||||
public resetValidation (): void {
|
||||
this.localFieldsErrors = {}
|
||||
this.localFormErrors = []
|
||||
this.localFieldsErrors = {}
|
||||
this.registry.forEach((field: FormularioField) => {
|
||||
field.resetValidation()
|
||||
})
|
||||
|
@ -1,4 +0,0 @@
|
||||
export const UNREGISTER_BEHAVIOR = {
|
||||
NONE: 'none',
|
||||
UNSET: 'unset',
|
||||
}
|
14
src/index.ts
14
src/index.ts
@ -1,14 +1,22 @@
|
||||
import { VueConstructor } from 'vue'
|
||||
import type { VueConstructor } from 'vue'
|
||||
import type { Options } from '../types/plugin'
|
||||
|
||||
import Formulario, { FormularioOptions } from '@/Formulario.ts'
|
||||
import Formulario from '@/Formulario'
|
||||
|
||||
import FormularioField from '@/FormularioField.vue'
|
||||
import FormularioFieldGroup from '@/FormularioFieldGroup.vue'
|
||||
import FormularioForm from '@/FormularioForm.vue'
|
||||
|
||||
export {
|
||||
Formulario,
|
||||
FormularioField,
|
||||
FormularioFieldGroup,
|
||||
FormularioForm,
|
||||
}
|
||||
|
||||
export default {
|
||||
Formulario,
|
||||
install (Vue: VueConstructor, options?: FormularioOptions): void {
|
||||
install (Vue: VueConstructor, options?: Options): void {
|
||||
Vue.component('FormularioField', FormularioField)
|
||||
Vue.component('FormularioFieldGroup', FormularioFieldGroup)
|
||||
Vue.component('FormularioForm', FormularioForm)
|
||||
|
9
src/shims-ext.d.ts
vendored
9
src/shims-ext.d.ts
vendored
@ -3,12 +3,7 @@ import Formulario from '@/Formulario'
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$formulario: Formulario;
|
||||
$route: VueRoute;
|
||||
$t: Function;
|
||||
$tc: Function;
|
||||
}
|
||||
|
||||
interface VueRoute {
|
||||
path: string;
|
||||
$t: any;
|
||||
$tc: any;
|
||||
}
|
||||
}
|
||||
|
27
src/types.ts
27
src/types.ts
@ -1,10 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import { Violation } from '@/validation/validator'
|
||||
import type { Violation } from '../types/validation'
|
||||
|
||||
export interface FormularioForm extends Vue {
|
||||
runValidation(): Promise<Record<string, Violation[]>>;
|
||||
resetValidation(): void;
|
||||
}
|
||||
import Vue from 'vue'
|
||||
|
||||
export interface FormularioField extends Vue {
|
||||
hasModel: boolean;
|
||||
@ -14,25 +10,6 @@ export interface FormularioField extends Vue {
|
||||
resetValidation(): void;
|
||||
}
|
||||
|
||||
export type FormularioFieldContext<T> = {
|
||||
model: T;
|
||||
name: string;
|
||||
runValidation(): Promise<Violation[]>;
|
||||
violations: Violation[];
|
||||
errors: string[];
|
||||
allErrors: string[];
|
||||
}
|
||||
|
||||
export interface FormularioFieldModelGetConverter {
|
||||
<U, T>(value: U|Empty): U|T|Empty;
|
||||
}
|
||||
|
||||
export interface FormularioFieldModelSetConverter {
|
||||
<T, U>(curr: U|T, prev: U|Empty): U|T;
|
||||
}
|
||||
|
||||
export type Empty = undefined | null
|
||||
|
||||
export enum TYPE {
|
||||
ARRAY = 'ARRAY',
|
||||
BIGINT = 'BIGINT',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import has from '@/utils/has.ts'
|
||||
import has from '@/utils/has'
|
||||
|
||||
/**
|
||||
* Create a new object by copying properties of base and mergeWith.
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
import type {
|
||||
ValidationContext,
|
||||
ValidationMessageI18NFn,
|
||||
} from '@/validation/validator'
|
||||
} from '../../types/validation'
|
||||
|
||||
/**
|
||||
* Message builders, names match rules names, see @/validation/rules
|
||||
|
@ -1,9 +1,10 @@
|
||||
import isUrl from 'is-url'
|
||||
import { has, regexForFormat, shallowEquals } from '@/utils'
|
||||
import {
|
||||
import type {
|
||||
ValidationContext,
|
||||
ValidationRuleFn,
|
||||
} from '@/validation/validator'
|
||||
} from '../../types/validation'
|
||||
|
||||
import isUrl from 'is-url'
|
||||
import { has, regexForFormat, shallowEquals } from '@/utils'
|
||||
|
||||
const rules: Record<string, ValidationRuleFn> = {
|
||||
/**
|
||||
|
@ -1,42 +1,14 @@
|
||||
import type {
|
||||
ValidationContext,
|
||||
ValidationMessageFn,
|
||||
ValidationRuleFn,
|
||||
Validator,
|
||||
ValidatorGroup,
|
||||
Violation,
|
||||
} from '../../types/validation'
|
||||
|
||||
import { has, snakeToCamel } from '@/utils'
|
||||
|
||||
export interface Validator {
|
||||
(context: ValidationContext): Promise<Violation|null>;
|
||||
}
|
||||
|
||||
export interface Violation {
|
||||
message: string;
|
||||
rule: string|null;
|
||||
args: any[];
|
||||
context: ValidationContext|null;
|
||||
}
|
||||
|
||||
export interface ValidationRuleFn {
|
||||
(context: ValidationContext, ...args: any[]): Promise<boolean>|boolean;
|
||||
}
|
||||
|
||||
export interface ValidationMessageFn {
|
||||
(context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationMessageI18NFn {
|
||||
(vm: Vue, context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationContext {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
// If wrapped in a FormulateForm, the value of other form fields.
|
||||
formValues: Record<string, any>;
|
||||
// The validation name to be used
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ValidatorGroup = {
|
||||
validators: Validator[];
|
||||
bail: boolean;
|
||||
}
|
||||
|
||||
export function createValidator (
|
||||
ruleFn: ValidationRuleFn,
|
||||
ruleName: string|null,
|
||||
|
@ -4,7 +4,6 @@ import { mount } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
|
||||
import Formulario from '@/index.ts'
|
||||
import FormularioFieldGroup from '@/FormularioFieldGroup.vue'
|
||||
import FormularioForm from '@/FormularioForm.vue'
|
||||
|
||||
Vue.use(Formulario)
|
||||
|
34
types/field.d.ts
vendored
Normal file
34
types/field.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import type { Violation } from './validation'
|
||||
|
||||
export type Empty = undefined | null
|
||||
|
||||
export type Context<T> = {
|
||||
model: T;
|
||||
name: string;
|
||||
path: string;
|
||||
violations: Violation[];
|
||||
errors: string[];
|
||||
allErrors: string[];
|
||||
runValidation(): Promise<Violation[]>;
|
||||
}
|
||||
|
||||
export interface ModelGetConverter {
|
||||
<U, T>(value: U|Empty): U|T|Empty;
|
||||
}
|
||||
|
||||
export interface ModelSetConverter {
|
||||
<T, U>(curr: U|T, prev: U|Empty): U|T;
|
||||
}
|
||||
|
||||
/**
|
||||
* - 'demand': triggers validation on manual call
|
||||
* - 'live': triggers validation on any changes
|
||||
* - 'submit': triggers validation on form submit event
|
||||
*/
|
||||
export type ValidationBehaviour = 'demand' | 'live' | 'submit'
|
||||
|
||||
/**
|
||||
* - 'none': no any specific effects
|
||||
* - 'unset': the value under field's path will be unset and path will be removed from the state
|
||||
*/
|
||||
export type UnregisterBehaviour = 'none' | 'unset'
|
17
types/form.d.ts
vendored
Normal file
17
types/form.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import type { DefineComponent } from './vue'
|
||||
import type { Violation } from './validation'
|
||||
|
||||
export type FormularioFormConstructor = DefineComponent<{
|
||||
id?: string;
|
||||
state?: Record<string, unknown>;
|
||||
fieldsErrors?: Record<string, string[]>;
|
||||
formErrors?: string[];
|
||||
}, {
|
||||
setErrors ({ formErrors, fieldsErrors }: {
|
||||
formErrors?: string[];
|
||||
fieldsErrors?: Record<string, string[]>;
|
||||
}): void;
|
||||
runValidation(): Promise<Record<string, Violation[]>>;
|
||||
hasValidationErrors (): Promise<boolean>;
|
||||
resetValidation(): void;
|
||||
}>
|
9
types/plugin.d.ts
vendored
Normal file
9
types/plugin.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import type {
|
||||
ValidationMessageI18NFn,
|
||||
ValidationRuleFn
|
||||
} from './validation'
|
||||
|
||||
export interface Options {
|
||||
validationRules?: Record<string, ValidationRuleFn>;
|
||||
validationMessages?: Record<string, ValidationMessageI18NFn|string>;
|
||||
}
|
38
types/validation.d.ts
vendored
Normal file
38
types/validation.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
import { Vue } from 'vue/types/vue'
|
||||
|
||||
export interface ValidationContext {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
// If wrapped in a FormulateForm, the value of other form fields.
|
||||
formValues: Record<string, any>;
|
||||
// The validation name to be used
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Violation {
|
||||
message: string;
|
||||
rule: string|null;
|
||||
args: any[];
|
||||
context: ValidationContext|null;
|
||||
}
|
||||
|
||||
export interface Validator {
|
||||
(context: ValidationContext): Promise<Violation|null>;
|
||||
}
|
||||
|
||||
export type ValidatorGroup = {
|
||||
validators: Validator[];
|
||||
bail: boolean;
|
||||
}
|
||||
|
||||
export interface ValidationRuleFn {
|
||||
(context: ValidationContext, ...args: any[]): Promise<boolean>|boolean;
|
||||
}
|
||||
|
||||
export interface ValidationMessageFn {
|
||||
(context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationMessageI18NFn {
|
||||
(vm: Vue, context: ValidationContext, ...args: any[]): string;
|
||||
}
|
3
types/vue.d.ts
vendored
Normal file
3
types/vue.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import type { Vue, VueConstructor } from 'vue/types/vue'
|
||||
|
||||
export type DefineComponent<Props, Methods> = VueConstructor<Vue & Required<Props> & Methods>
|
Loading…
Reference in New Issue
Block a user