1
0
mirror of synced 2024-11-21 21:06:04 +03:00

chore: Additional typehints, Object/ObjectType replaced with Record<string, any>

This commit is contained in:
Zaytsev Kirill 2020-10-18 16:41:57 +03:00
parent a3eb14a745
commit 54bf820824
16 changed files with 211 additions and 230 deletions

View File

@ -6,11 +6,14 @@ module.exports = {
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',
],
@ -19,17 +22,18 @@ module.exports = {
},
rules: {
// allow paren-less arrow functions
'@typescript-eslint/ban-ts-ignore': 'off', // @TODO
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off', // @TODO
'@typescript-eslint/no-unused-vars': ['error'], // @TODO
'arrow-parens': 0,
'comma-dangle': ['error', 'only-multiline'],
'indent': ['error', 4, { SwitchCase: 1 }],
'max-depth': ['error', 3],
'max-lines-per-function': ['error', 40],
'no-console': ['warn', {allow: ['warn', 'error']}],
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'vue/html-closing-bracket-spacing': ['error', {
startTag: 'never',
endTag: 'never',
@ -42,4 +46,12 @@ module.exports = {
ignores: [],
}],
},
overrides: [{
files: ['*/**/shims-*.d.ts'],
rules: {
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
}],
}

View File

@ -1,26 +1,25 @@
import nanoid from 'nanoid/non-secure'
import { AxiosResponse, AxiosError } from '@/axios.types'
import { ObjectType } from '@/common.types'
interface FileItem {
uuid: string
name: string
path: string | false
progress: number | false
error: any | false
complete: boolean
file: File
justFinished: boolean
removeFile(): void
previewData: string | false
uuid: string;
name: string;
path: string | false;
progress: number | false;
error: any | false;
complete: boolean;
file: File;
justFinished: boolean;
removeFile(): void;
previewData: string | false;
}
interface ProgressSetter {
(progress: number): void
(progress: number): void;
}
interface ErrorHandler {
(error: AxiosError): any
(error: AxiosError): any;
}
// noinspection JSUnusedGlobalSymbols
@ -32,11 +31,11 @@ class FileUpload {
public input: DataTransfer
public fileList: FileList
public files: FileItem[]
public options: ObjectType
public context: ObjectType
public options: Record<string, any>
public context: Record<string, any>
public results: any[] | boolean
constructor (input: DataTransfer, context: ObjectType = {}, options: ObjectType = {}) {
constructor (input: DataTransfer, context: Record<string, any> = {}, options: Record<string, any> = {}) {
this.input = input
this.fileList = input.files
this.files = []
@ -54,7 +53,7 @@ class FileUpload {
* Given a pre-existing array of files, create a faux FileList.
* @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }]
*/
rehydrateFileList (items: any[]) {
rehydrateFileList (items: any[]): void {
const fauxFileList = items.reduce((fileList, item) => {
const key = this.options ? this.options.fileUrlKey : 'url'
const url = item[key]
@ -75,7 +74,7 @@ class FileUpload {
* Produce an array of files and alert the callback.
* @param {FileList} fileList
*/
addFileList (fileList: FileList) {
addFileList (fileList: FileList): void {
for (let i = 0; i < fileList.length; i++) {
const file: File = fileList[i]
const uuid = nanoid()
@ -98,7 +97,7 @@ class FileUpload {
/**
* Check if the file has an.
*/
hasUploader () {
hasUploader (): boolean {
return !!this.context.uploader
}
@ -108,7 +107,7 @@ class FileUpload {
*
* https://github.com/axios/axios/issues/737
*/
uploaderIsAxios () {
uploaderIsAxios (): boolean {
return this.hasUploader &&
typeof this.context.uploader.request === 'function' &&
typeof this.context.uploader.get === 'function' &&
@ -119,7 +118,7 @@ class FileUpload {
/**
* Get a new uploader function.
*/
getUploader (...args: [File, ProgressSetter, ErrorHandler, ObjectType]) {
getUploader (...args: [File, ProgressSetter, ErrorHandler, Record<string, any>]) {
if (this.uploaderIsAxios()) {
const data = new FormData()
data.append(this.context.name || 'file', args[0])
@ -184,7 +183,7 @@ class FileUpload {
/**
* Remove a file from the uploader (and the file list)
*/
removeFile (uuid: string) {
removeFile (uuid: string): void {
this.files = this.files.filter(file => file.uuid !== uuid)
this.context.performValidation()
if (window && this.fileList instanceof FileList) {
@ -223,7 +222,7 @@ class FileUpload {
return this.files
}
toString () {
toString (): string {
const descriptor = this.files.length ? this.files.length + ' files' : 'empty'
return this.results ? JSON.stringify(this.results, null, ' ') : `FileUpload(${descriptor})`
}

View File

@ -15,18 +15,15 @@ export default class FormSubmission {
/**
* Determine if the form has any validation errors.
*
* @return {Promise} resolves a boolean
*/
hasValidationErrors () {
hasValidationErrors (): Promise<boolean> {
return (this.form as any).hasValidationErrors()
}
/**
* Asynchronously generate the values payload of this form.
* @return {Promise} resolves to json
*/
values () {
values (): Promise<Record<string, any>> {
return new Promise((resolve, reject) => {
const form = this.form as any
const pending = []
@ -36,9 +33,10 @@ export default class FormSubmission {
if (
Object.prototype.hasOwnProperty.call(values, key) &&
typeof form.proxy[key] === 'object' &&
form.proxy[key] instanceof FileUpload) {
form.proxy[key] instanceof FileUpload
) {
pending.push(
form.proxy[key].upload().then((data: Object) => Object.assign(values, { [key]: data }))
form.proxy[key].upload().then((data: Record<string, any>) => Object.assign(values, { [key]: data }))
)
}
}

View File

@ -12,27 +12,26 @@ import merge from '@/utils/merge'
import FormularioForm from '@/FormularioForm.vue'
import FormularioInput from '@/FormularioInput.vue'
import FormularioGrouping from '@/FormularioGrouping.vue'
import { ObjectType } from '@/common.types'
import { ValidationContext } from '@/validation/types'
interface ErrorHandler {
(error: any, formName?: string): any
(error: any, formName?: string): any;
}
interface FormularioOptions {
components?: { [name: string]: VueConstructor }
plugins?: any[]
library?: any
rules?: any
mimes?: any
locale?: any
uploader?: any
uploadUrl?: any
fileUrlKey?: any
errorHandler?: ErrorHandler
uploadJustCompleteDuration?: any
validationMessages?: any
idPrefix?: string
components?: { [name: string]: VueConstructor };
plugins?: any[];
library?: any;
rules?: any;
mimes?: any;
locale?: any;
uploader?: any;
uploadUrl?: any;
fileUrlKey?: any;
errorHandler?: ErrorHandler;
uploadJustCompleteDuration?: any;
validationMessages?: any;
idPrefix?: string;
}
// noinspection JSUnusedGlobalSymbols
@ -122,7 +121,7 @@ export default class Formulario {
/**
* Get validation rules by merging any passed in with global rules.
*/
rules (rules: Object = {}) {
rules (rules: Record<string, any> = {}) {
return { ...this.options.rules, ...rules }
}
@ -206,7 +205,7 @@ export default class Formulario {
/**
* Set the form values.
*/
setValues (formName: string, values?: ObjectType) {
setValues (formName: string, values?: Record<string, any>) {
if (values) {
const form = this.registry.get(formName) as FormularioForm
// @ts-ignore
@ -224,7 +223,7 @@ export default class Formulario {
/**
* Get the global upload url.
*/
getUploadUrl () {
getUploadUrl (): string | boolean {
return this.options.uploadUrl || false
}
@ -239,7 +238,7 @@ export default class Formulario {
/**
* Create a new instance of an upload.
*/
createUpload (data: DataTransfer, context: ObjectType) {
createUpload (data: DataTransfer, context: Record<string, any>) {
return new FileUpload(data, context, this.options)
}
}

View File

@ -14,32 +14,31 @@ import {
Watch,
} from 'vue-property-decorator'
import { arrayify, getNested, has, setNested, shallowEqualObjects } from '@/libs/utils'
import { ObjectType } from '@/common.types'
import Registry from '@/libs/registry'
import FormSubmission from '@/FormSubmission'
import FormularioInput from '@/FormularioInput.vue'
@Component
export default class FormularioForm extends Vue {
@Provide() formularioFieldValidation (errorObject) {
return this.$emit('validation', errorObject)
@Provide() formularioFieldValidation (errorObject): void {
this.$emit('validation', errorObject)
}
@Provide() formularioRegister = this.register
@Provide() formularioDeregister = this.deregister
@Provide() formularioSetter = this.setFieldValue
@Provide() getFormValues = () => this.proxy
@Provide() getFormValues = (): Record<string, any> => this.proxy
@Provide() observeErrors = this.addErrorObserver
@Provide() path: string = ''
@Provide() path = ''
@Provide() removeErrorObserver (observer) {
@Provide() removeErrorObserver (observer): void {
this.errorObservers = this.errorObservers.filter(obs => obs.callback !== observer)
}
@Model('input', {
type: Object,
default: () => ({})
}) readonly formularioValue!: Object
}) readonly formularioValue!: Record<string, any>
@Prop({
type: [String, Boolean],
@ -49,31 +48,31 @@ export default class FormularioForm extends Vue {
@Prop({
type: [Object, Boolean],
default: false
}) readonly values!: Object | Boolean
}) readonly values!: Record<string, any> | boolean
@Prop({
type: [Object, Boolean],
default: false
}) readonly errors!: Object | Boolean
}) readonly errors!: Record<string, any> | boolean
@Prop({
type: Array,
default: () => ([])
}) readonly formErrors!: []
public proxy: Object = {}
public proxy: Record<string, any> = {}
registry: Registry = new Registry(this)
childrenShouldShowErrors: boolean = false
childrenShouldShowErrors = false
formShouldShowErrors: boolean = false
formShouldShowErrors = false
errorObservers: [] = []
namedErrors: [] = []
namedFieldErrors: Object = {}
namedFieldErrors: Record<string, any> = {}
get mergedFormErrors () {
return this.formErrors.concat(this.namedErrors)
@ -95,11 +94,11 @@ export default class FormularioForm extends Vue {
return errors
}
get hasFormErrorObservers () {
get hasFormErrorObservers (): boolean {
return this.errorObservers.some(o => o.type === 'form')
}
get hasInitialValue () {
get hasInitialValue (): boolean {
return (
(this.formularioValue && typeof this.formularioValue === 'object') ||
(this.values && typeof this.values === 'object') ||
@ -107,14 +106,14 @@ export default class FormularioForm extends Vue {
)
}
get isVmodeled () {
get isVmodeled (): boolean {
return !!(has(this.$options.propsData, 'formularioValue') &&
this._events &&
Array.isArray(this._events.input) &&
this._events.input.length)
}
get initialValues () {
get initialValues (): Record<string, any> {
if (
has(this.$options.propsData, 'formularioValue') &&
typeof this.formularioValue === 'object'
@ -136,50 +135,50 @@ export default class FormularioForm extends Vue {
}
@Watch('formularioValue', { deep: true })
onFormularioValueChanged (values) {
onFormularioValueChanged (values): void {
if (this.isVmodeled && values && typeof values === 'object') {
this.setValues(values)
}
}
@Watch('mergedFormErrors')
onMergedFormErrorsChanged (errors) {
onMergedFormErrorsChanged (errors): void {
this.errorObservers
.filter(o => o.type === 'form')
.forEach(o => o.callback(errors))
}
@Watch('mergedFieldErrors', { immediate: true })
onMergedFieldErrorsChanged (errors) {
onMergedFieldErrorsChanged (errors): void {
this.errorObservers
.filter(o => o.type === 'input')
.forEach(o => o.callback(errors[o.field] || []))
}
created () {
created (): void {
this.$formulario.register(this)
this.applyInitialValues()
}
destroyed () {
destroyed (): void {
this.$formulario.deregister(this)
}
public register (field: string, component: FormularioInput) {
public register (field: string, component: FormularioInput): void {
this.registry.register(field, component)
}
public deregister (field: string) {
public deregister (field: string): void {
this.registry.remove(field)
}
applyErrors ({ formErrors, inputErrors }) {
applyErrors ({ formErrors, inputErrors }): void {
// given an object of errors, apply them to this form
this.namedErrors = formErrors
this.namedFieldErrors = inputErrors
}
addErrorObserver (observer) {
addErrorObserver (observer): void {
if (!this.errorObservers.find(obs => observer.callback === obs.callback)) {
this.errorObservers.push(observer)
if (observer.type === 'form') {
@ -190,13 +189,13 @@ export default class FormularioForm extends Vue {
}
}
registerErrorComponent (component) {
registerErrorComponent (component): void {
if (!this.errorComponents.includes(component)) {
this.errorComponents.push(component)
}
}
formSubmitted () {
formSubmitted (): Promise<void> {
// perform validation here
this.showErrors()
const submission = new FormSubmission(this)
@ -208,17 +207,16 @@ export default class FormularioForm extends Vue {
this.$emit('submit', data)
return data
}
return undefined
})
}
applyInitialValues () {
applyInitialValues (): void {
if (this.hasInitialValue) {
this.proxy = this.initialValues
}
}
setFieldValue (field, value) {
setFieldValue (field, value): void {
if (value === undefined) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [field]: value, ...proxy } = this.proxy
@ -229,21 +227,21 @@ export default class FormularioForm extends Vue {
this.$emit('input', Object.assign({}, this.proxy))
}
hasValidationErrors () {
hasValidationErrors (): Promise<boolean> {
return Promise.all(this.registry.reduce((resolvers, cmp) => {
resolvers.push(cmp.performValidation() && cmp.getValidationErrors())
return resolvers
}, [])).then(errorObjects => errorObjects.some(item => item.hasErrors))
}
showErrors () {
showErrors (): void {
this.childrenShouldShowErrors = true
this.registry.forEach((input: FormularioInput) => {
input.formShouldShowErrors = true
})
}
hideErrors () {
hideErrors (): void {
this.childrenShouldShowErrors = false
this.registry.forEach((input: FormularioInput) => {
input.formShouldShowErrors = false
@ -251,7 +249,7 @@ export default class FormularioForm extends Vue {
})
}
setValues (values: ObjectType) {
setValues (values: Record<string, any>): void {
// Collect all keys, existing and incoming
const keys = Array.from(new Set(Object.keys(values).concat(Object.keys(this.proxy))))
keys.forEach(field => {

View File

@ -26,7 +26,6 @@ import {
} from 'vue-property-decorator'
import { shallowEqualObjects, parseRules, snakeToCamel, has, arrayify, groupBails } from './libs/utils'
import { ValidationError } from '@/validation/types'
import { ObjectType } from '@/common.types'
const ERROR_BEHAVIOR = {
BLUR: 'blur',
@ -37,10 +36,10 @@ const ERROR_BEHAVIOR = {
@Component({ inheritAttrs: false })
export default class FormularioInput extends Vue {
@Inject({ default: undefined }) formularioSetter!: Function|undefined
@Inject({ default: () => () => ({}) }) formularioFieldValidation!: Function
@Inject({ default: () => (): void => {} }) formularioFieldValidation!: Function
@Inject({ default: undefined }) formularioRegister!: Function|undefined
@Inject({ default: undefined }) formularioDeregister!: Function|undefined
@Inject({ default: () => () => ({}) }) getFormValues!: Function
@Inject({ default: () => (): Record<string, any> => ({}) }) getFormValues!: Function
@Inject({ default: undefined }) observeErrors!: Function|undefined
@Inject({ default: undefined }) removeErrorObserver!: Function|undefined
@Inject({ default: '' }) path!: string
@ -74,12 +73,12 @@ export default class FormularioInput extends Vue {
@Prop({
type: Object,
default: () => ({}),
}) validationRules!: ObjectType
}) validationRules!: Record<string, any>
@Prop({
type: Object,
default: () => ({}),
}) validationMessages!: ObjectType
}) validationMessages!: Record<string, any>
@Prop({
type: [Array, String, Boolean],
@ -96,23 +95,23 @@ export default class FormularioInput extends Vue {
@Prop({ default: false }) disableErrors!: boolean
@Prop({ default: true }) preventWindowDrops!: boolean
@Prop({ default: 'preview' }) imageBehavior!: string
@Prop({ default: false }) uploader!: Function|Object|boolean
@Prop({ default: false }) uploader!: Function|Record<string, any>|boolean
@Prop({ default: false }) uploadUrl!: string|boolean
@Prop({ default: 'live' }) uploadBehavior!: string
defaultId: string = this.$formulario.nextId(this)
localAttributes: ObjectType = {}
localAttributes: Record<string, any> = {}
localErrors: ValidationError[] = []
proxy: ObjectType = this.getInitialValue()
proxy: Record<string, any> = this.getInitialValue()
behavioralErrorVisibility: boolean = this.errorBehavior === 'live'
formShouldShowErrors: boolean = false
formShouldShowErrors = false
validationErrors: [] = []
pendingValidation: Promise<any> = Promise.resolve()
// These registries are used for injected messages registrants only (mostly internal).
ruleRegistry: [] = []
messageRegistry: ObjectType = {}
messageRegistry: Record<string, any> = {}
get context () {
get context (): Record<string, any> {
return this.defineModel({
attributes: this.elementAttributes,
blurHandler: this.blurHandler.bind(this),
@ -164,7 +163,7 @@ export default class FormularioInput extends Vue {
/**
* Reducer for attributes that will be applied to each core input element.
*/
get elementAttributes () {
get elementAttributes (): Record<string, any> {
const attrs = Object.assign({}, this.localAttributes)
// pass the ID prop through to the root element
if (this.id) {
@ -188,14 +187,14 @@ export default class FormularioInput extends Vue {
/**
* Return the elements name, or select a fallback.
*/
get nameOrFallback () {
get nameOrFallback (): string {
return this.path !== '' ? `${this.path}.${this.name}` : this.name
}
/**
* Determine if an input has a user-defined name.
*/
get hasGivenName () {
get hasGivenName (): boolean {
return typeof this.name !== 'boolean'
}
@ -210,21 +209,21 @@ export default class FormularioInput extends Vue {
* Use the uploadURL on the input if it exists, otherwise use the uploadURL
* that is defined as a plugin option.
*/
get mergedUploadUrl () {
get mergedUploadUrl (): string | boolean {
return this.uploadUrl || this.$formulario.getUploadUrl()
}
/**
* Does this computed property have errors
*/
get hasErrors () {
get hasErrors (): boolean {
return this.allErrors.length > 0
}
/**
* Returns if form has actively visible errors (of any kind)
*/
get hasVisibleErrors () {
get hasVisibleErrors (): boolean {
return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length)
}
@ -275,12 +274,12 @@ export default class FormularioInput extends Vue {
}
@Watch('$attrs', { deep: true })
onAttrsChanged (value) {
onAttrsChanged (value): void {
this.updateLocalAttributes(value)
}
@Watch('proxy')
onProxyChanged (newValue, oldValue) {
onProxyChanged (newValue, oldValue): void {
this.performValidation()
if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue
@ -288,18 +287,18 @@ export default class FormularioInput extends Vue {
}
@Watch('formularioValue')
onFormularioValueChanged (newValue, oldValue) {
onFormularioValueChanged (newValue, oldValue): void {
if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
this.context.model = newValue
}
}
@Watch('showValidationErrors', { immediate: true })
onShowValidationErrorsChanged (val) {
onShowValidationErrorsChanged (val): void {
this.$emit('error-visibility', val)
}
created () {
created (): void {
this.applyInitialValue()
if (this.formularioRegister && typeof this.formularioRegister === 'function') {
this.formularioRegister(this.nameOrFallback, this)
@ -312,7 +311,7 @@ export default class FormularioInput extends Vue {
}
// noinspection JSUnusedGlobalSymbols
beforeDestroy () {
beforeDestroy (): void {
if (!this.disableErrors && typeof this.removeErrorObserver === 'function') {
this.removeErrorObserver(this.setErrors)
}
@ -346,7 +345,7 @@ export default class FormularioInput extends Vue {
/**
* Set the value from a model.
*/
modelSetter (value) {
modelSetter (value): void {
if (!shallowEqualObjects(value, this.proxy)) {
this.proxy = value
}
@ -366,16 +365,16 @@ export default class FormularioInput extends Vue {
}
}
getInitialValue () {
if (has(this.$options.propsData as ObjectType, 'value')) {
getInitialValue (): any {
if (has(this.$options.propsData as Record<string, any>, 'value')) {
return this.value
} else if (has(this.$options.propsData as ObjectType, 'formularioValue')) {
} else if (has(this.$options.propsData as Record<string, any>, 'formularioValue')) {
return this.formularioValue
}
return ''
}
applyInitialValue () {
applyInitialValue (): void {
// This should only be run immediately on created and ensures that the
// proxy and the model are both the same before any additional registration.
if (!shallowEqualObjects(this.context.model, this.proxy)) {
@ -383,7 +382,7 @@ export default class FormularioInput extends Vue {
}
}
updateLocalAttributes (value) {
updateLocalAttributes (value): void {
if (!shallowEqualObjects(value, this.localAttributes)) {
this.localAttributes = value
}
@ -432,7 +431,7 @@ export default class FormularioInput extends Vue {
})
}
didValidate (messages) {
didValidate (messages): void {
const validationChanged = !shallowEqualObjects(messages, this.validationErrors)
this.validationErrors = messages
if (validationChanged) {
@ -461,7 +460,7 @@ export default class FormularioInput extends Vue {
}
}
getMessageFunc (ruleName) {
getMessageFunc (ruleName: string) {
ruleName = snakeToCamel(ruleName)
if (this.messages && typeof this.messages[ruleName] !== 'undefined') {
switch (typeof this.messages[ruleName]) {
@ -497,11 +496,11 @@ export default class FormularioInput extends Vue {
}
}
setErrors (errors) {
setErrors (errors): void {
this.localErrors = arrayify(errors)
}
registerRule (rule, args, ruleName, message = null) {
registerRule (rule, args, ruleName, message = null): void {
if (!this.ruleRegistry.some(r => r[2] === ruleName)) {
// These are the raw rule format since they will be used directly.
this.ruleRegistry.push([rule, args, ruleName])
@ -511,7 +510,7 @@ export default class FormularioInput extends Vue {
}
}
removeRule (key) {
removeRule (key): void {
const ruleIndex = this.ruleRegistry.findIndex(r => r[2] === key)
if (ruleIndex >= 0) {
this.ruleRegistry.splice(ruleIndex, 1)

View File

@ -46,14 +46,14 @@ const validationMessages = {
/**
* The value is not a letter.
*/
alpha (vm: FormularioInput, context: Object): string {
alpha (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.alpha', context)
},
/**
* Rule: checks if the value is alpha numeric
*/
alphanumeric (vm: FormularioInput, context: Object): string {
alphanumeric (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.alphanumeric', context)
},
@ -116,7 +116,7 @@ const validationMessages = {
/**
* Value is an allowed value.
*/
in: function (vm: FormularioInput, context: ValidationContext) {
in: function (vm: FormularioInput, context: ValidationContext): string {
if (typeof context.value === 'string' && context.value) {
return vm.$t('validation.in.string', context)
}
@ -127,14 +127,14 @@ const validationMessages = {
/**
* Value is not a match.
*/
matches (vm: FormularioInput, context: ValidationContext) {
matches (vm: FormularioInput, context: ValidationContext): string {
return vm.$t('validation.matches.default', context)
},
/**
* The maximum value allowed.
*/
max (vm: FormularioInput, context: ValidationContext) {
max (vm: FormularioInput, context: ValidationContext): string {
const maximum = context.args[0] as number
if (Array.isArray(context.value)) {
@ -150,7 +150,7 @@ const validationMessages = {
/**
* The (field-level) error message for mime errors.
*/
mime (vm: FormularioInput, context: ValidationContext) {
mime (vm: FormularioInput, context: ValidationContext): string {
const types = context.args[0]
if (types) {
@ -163,7 +163,7 @@ const validationMessages = {
/**
* The maximum value allowed.
*/
min (vm: FormularioInput, context: ValidationContext) {
min (vm: FormularioInput, context: ValidationContext): string {
const minimum = context.args[0] as number
if (Array.isArray(context.value)) {
@ -179,35 +179,35 @@ const validationMessages = {
/**
* The field is not an allowed value
*/
not (vm: FormularioInput, context: Object) {
not (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.not.default', context)
},
/**
* The field is not a number
*/
number (vm: FormularioInput, context: Object) {
number (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.number.default', context)
},
/**
* Required field.
*/
required (vm: FormularioInput, context: Object) {
required (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.required.default', context)
},
/**
* Starts with specified value
*/
startsWith (vm: FormularioInput, context: Object) {
startsWith (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.startsWith.default', context)
},
/**
* Value is not a url.
*/
url (vm: FormularioInput, context: Object) {
url (vm: FormularioInput, context: Record<string, any>): string {
return vm.$t('validation.url.default', context)
}
}

View File

@ -1,2 +0,0 @@
export type ArrayType = [any]
export type ObjectType = { [key: string]: any }

View File

@ -1,5 +1,4 @@
import { shallowEqualObjects, has, getNested } from './utils'
import { ObjectType } from '@/common.types'
import FormularioForm from '@/FormularioForm.vue'
import FormularioInput from '@/FormularioInput.vue'
@ -23,36 +22,33 @@ export default class Registry {
/**
* Add an item to the registry.
*/
add (name: string, component: FormularioInput) {
add (name: string, component: FormularioInput): void {
this.registry.set(name, component)
return this
}
/**
* Remove an item from the registry.
* @param {string} name
*/
remove (name: string) {
remove (name: string): void {
this.registry.delete(name)
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [name]: value, ...newProxy } = this.ctx.proxy
// @ts-ignore
this.ctx.proxy = newProxy
return this
}
/**
* Check if the registry has the given key.
*/
has (key: string) {
has (key: string): boolean {
return this.registry.has(key)
}
/**
* Check if the registry has elements, that equals or nested given key
*/
hasNested (key: string) {
hasNested (key: string): boolean {
for (const i of this.registry.keys()) {
if (i === key || i.includes(key + '.')) {
return true
@ -72,7 +68,7 @@ export default class Registry {
/**
* Get registry value for key or nested to given key
*/
getNested (key: string) {
getNested (key: string): Map<string, FormularioInput> {
const result = new Map()
for (const i of this.registry.keys()) {
@ -87,7 +83,7 @@ export default class Registry {
/**
* Map over the registry (recursively).
*/
map (mapper: Function) {
map (mapper: Function): Record<string, any> {
const value = {}
this.registry.forEach((component, field) => Object.assign(value, { [field]: mapper(component, field) }))
return value
@ -96,7 +92,7 @@ export default class Registry {
/**
* Map over the registry (recursively).
*/
forEach (callback: Function) {
forEach (callback: Function): void {
this.registry.forEach((component, field) => {
callback(component, field)
})
@ -114,13 +110,13 @@ export default class Registry {
* @param {string} field name of the field.
* @param {FormularioForm} component the actual component instance.
*/
register (field: string, component: FormularioInput) {
register (field: string, component: FormularioInput): void {
if (this.registry.has(field)) {
return false
return
}
this.registry.set(field, component)
const hasVModelValue = has(component.$options.propsData as ObjectType, 'formularioValue')
const hasValue = has(component.$options.propsData as ObjectType, 'value')
const hasVModelValue = has(component.$options.propsData as Record<string, any>, 'formularioValue')
const hasValue = has(component.$options.propsData as Record<string, any>, 'value')
if (
!hasVModelValue &&
// @ts-ignore

View File

@ -1,16 +1,12 @@
import FileUpload from '@/FileUpload'
import {
ArrayType,
ObjectType,
} from '@/common.types'
/**
* Function to map over an object.
* @param {Object} original An object to map over
* @param {Function} callback
*/
export function map (original: ObjectType, callback: Function) {
const obj: ObjectType = {}
export function map (original: Record<string, any>, callback: Function): Record<string, any> {
const obj: Record<string, any> = {}
for (const key in original) {
if (Object.prototype.hasOwnProperty.call(original, key)) {
obj[key] = callback(key, original[key])
@ -19,7 +15,7 @@ export function map (original: ObjectType, callback: Function) {
return obj
}
export function shallowEqualObjects (objA: any, objB: any) {
export function shallowEqualObjects (objA: Record<string, any>, objB: Record<string, any>): boolean {
if (objA === objB) {
return true
}
@ -66,7 +62,7 @@ export function snakeToCamel (string: string | any) {
* If given parameter is not string, object ot array, result will be an empty array.
* @param {*} item
*/
export function arrayify (item: any) {
export function arrayify (item: any): any[] {
if (!item) {
return []
}
@ -102,7 +98,7 @@ export function parseRules (validation: any, rules: any): any[] {
* Given a string or function, parse it and return an array in the format
* [fn, [...arguments]]
*/
function parseRule (rule: any, rules: ObjectType) {
function parseRule (rule: any, rules: Record<string, any>) {
if (typeof rule === 'function') {
return [rule, []]
}
@ -243,12 +239,12 @@ export function isScalar (data: any) {
* A simple (somewhat non-comprehensive) cloneDeep function, valid for our use
* case of needing to unbind reactive watchers.
*/
export function cloneDeep (value: any) {
export function cloneDeep (value: any): any {
if (typeof value !== 'object') {
return value
}
const copy: ArrayType | ObjectType = Array.isArray(value) ? [] : {}
const copy: any | Record<string, any> = Array.isArray(value) ? [] : {}
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
@ -267,7 +263,7 @@ export function cloneDeep (value: any) {
* Given a locale string, parse the options.
* @param {string} locale
*/
export function parseLocale (locale: string) {
export function parseLocale (locale: string): string[] {
const segments = locale.split('-')
return segments.reduce((options: string[], segment: string) => {
if (options.length) {
@ -280,21 +276,14 @@ export function parseLocale (locale: string) {
/**
* Shorthand for Object.prototype.hasOwnProperty.call (space saving)
*/
export function has (ctx: ObjectType, prop: string): boolean {
export function has (ctx: Record<string, any>, prop: string): boolean {
return Object.prototype.hasOwnProperty.call(ctx, prop)
}
/**
* Set a unique Symbol identifier on an object.
*/
export function setId (o: object, id: Symbol) {
return Object.defineProperty(o, '__id', Object.assign(Object.create(null), { value: id || Symbol('uuid') }))
}
export function getNested (obj: ObjectType, field: string) {
export function getNested (obj: Record<string, any>, field: string): any {
const fieldParts = field.split('.')
let result: ObjectType = obj
let result: Record<string, any> = obj
for (const key in fieldParts) {
const matches = fieldParts[key].match(/(.+)\[(\d+)\]$/)
@ -315,10 +304,10 @@ export function getNested (obj: ObjectType, field: string) {
return result
}
export function setNested (obj: ObjectType, field: string, value: any) {
export function setNested (obj: Record<string, any>, field: string, value: any): void {
const fieldParts = field.split('.')
let subProxy: ObjectType = obj
let subProxy: Record<string, any> = obj
for (let i = 0; i < fieldParts.length; i++) {
const fieldPart = fieldParts[i]
const matches = fieldPart.match(/(.+)\[(\d+)\]$/)
@ -348,6 +337,4 @@ export function setNested (obj: ObjectType, field: string, value: any) {
}
}
}
return obj
}

20
src/shims-ext.d.ts vendored
View File

@ -1,20 +1,20 @@
import Formulario from '@/Formulario'
declare module 'vue/types/vue' {
interface VueRoute {
path: string
interface Vue {
$formulario: Formulario;
$route: VueRoute;
$t: Function;
$tc: Function;
}
interface Vue {
$formulario: Formulario,
$route: VueRoute,
$t: Function,
$tc: Function,
interface VueRoute {
path: string;
}
interface FormularioForm extends Vue {
name: string | boolean
proxy: Object
hasValidationErrors(): Promise<boolean>
name: string | boolean;
proxy: Record<string, any>;
hasValidationErrors(): Promise<boolean>;
}
}

4
src/shims-tsx.d.ts vendored
View File

@ -2,12 +2,10 @@ import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
[elem: string]: any;
}
}
}

View File

@ -1,35 +1,38 @@
import isPlainObject from 'is-plain-object'
import { ObjectType } from '@/common.types.ts'
import { has } from '@/libs/utils.ts'
/**
* Create a new object by copying properties of base and mergeWith.
* Note: arrays don't overwrite - they push
*
* @param {Object} base
* @param {Object} mergeWith
* @param {Object} a
* @param {Object} b
* @param {boolean} concatArrays
*/
export default function merge (base: ObjectType, mergeWith: ObjectType, concatArrays: boolean = true) {
const merged: ObjectType = {}
export default function merge (
a: Record<string, any>,
b: Record<string, any>,
concatArrays = true
): Record<string, any> {
const merged: Record<string, any> = {}
for (const key in base) {
if (has(mergeWith, key)) {
if (isPlainObject(mergeWith[key]) && isPlainObject(base[key])) {
merged[key] = merge(base[key], mergeWith[key], concatArrays)
} else if (concatArrays && Array.isArray(base[key]) && Array.isArray(mergeWith[key])) {
merged[key] = base[key].concat(mergeWith[key])
for (const key in a) {
if (has(b, key)) {
if (isPlainObject(b[key]) && isPlainObject(a[key])) {
merged[key] = merge(a[key], b[key], concatArrays)
} else if (concatArrays && Array.isArray(a[key]) && Array.isArray(b[key])) {
merged[key] = a[key].concat(b[key])
} else {
merged[key] = mergeWith[key]
merged[key] = b[key]
}
} else {
merged[key] = base[key]
merged[key] = a[key]
}
}
for (const prop in mergeWith) {
for (const prop in b) {
if (!has(merged, prop)) {
merged[prop] = mergeWith[prop]
merged[prop] = b[prop]
}
}

View File

@ -2,12 +2,11 @@
import isUrl from 'is-url'
import FileUpload from '../FileUpload'
import { shallowEqualObjects, regexForFormat, has } from '@/libs/utils'
import { ObjectType } from '@/common.types'
import { ValidatableData } from '@/validation/types'
interface ConfirmValidatableData extends ValidatableData {
getFormValues: () => ObjectType,
name: string,
getFormValues: () => Record<string, any>;
name: string;
}
/**
@ -33,7 +32,7 @@ export default {
/**
* Rule: checks if the value is only alpha
*/
alpha ({ value }: { value: string }, set: string = 'default'): Promise<boolean> {
alpha ({ value }: { value: string }, set = 'default'): Promise<boolean> {
const sets = {
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
latin: /^[a-zA-Z]+$/
@ -69,7 +68,7 @@ export default {
* Rule: checks if the value is between two other values
*/
between ({ value }: { value: string|number }, from: number|any = 0, to: number|any = 10, force?: string): Promise<boolean> {
return Promise.resolve((() => {
return Promise.resolve(((): boolean => {
if (from === null || to === null || isNaN(from) || isNaN(to)) {
return false
}
@ -92,7 +91,7 @@ export default {
* for password confirmations.
*/
confirm ({ value, getFormValues, name }: ConfirmValidatableData, field?: string): Promise<boolean> {
return Promise.resolve((() => {
return Promise.resolve(((): boolean => {
const formValues = getFormValues()
let confirmationFieldName = field
if (!confirmationFieldName) {
@ -168,7 +167,7 @@ export default {
mime ({ value }: { value: any }, ...types: string[]): Promise<boolean> {
if (value instanceof FileUpload) {
const files = value.getFiles()
const isMimeCorrect = (file: File) => types.includes(file.type)
const isMimeCorrect = (file: File): boolean => types.includes(file.type)
const allValid: boolean = files.reduce((valid: boolean, { file }) => valid && isMimeCorrect(file), true)
return Promise.resolve(allValid)
@ -181,7 +180,7 @@ export default {
* Check the minimum value of a particular.
*/
min ({ value }: { value: any }, minimum: number | any = 1, force?: string): Promise<boolean> {
return Promise.resolve((() => {
return Promise.resolve(((): boolean => {
if (Array.isArray(value)) {
minimum = !isNaN(minimum) ? Number(minimum) : minimum
return value.length >= minimum
@ -202,7 +201,7 @@ export default {
* Check the maximum value of a particular.
*/
max ({ value }: { value: any }, maximum: string | number = 10, force?: string): Promise<boolean> {
return Promise.resolve((() => {
return Promise.resolve(((): boolean => {
if (Array.isArray(value)) {
maximum = !isNaN(Number(maximum)) ? Number(maximum) : maximum
return value.length <= maximum
@ -239,7 +238,7 @@ export default {
* Rule: must be a value
*/
required ({ value }: { value: any }, isRequired: string|boolean = true): Promise<boolean> {
return Promise.resolve((() => {
return Promise.resolve(((): boolean => {
if (!isRequired || ['no', 'false'].includes(isRequired as string)) {
return true
}

View File

@ -1,17 +1,17 @@
interface ValidatableData {
value: any,
value: any;
}
interface ValidationContext {
args: any[]
name: string
value: any
args: any[];
name: string;
value: any;
}
interface ValidationError {
rule?: string
context?: any
message: string
rule?: string;
context?: any;
message: string;
}
export { ValidatableData }

View File

@ -265,9 +265,8 @@ describe('FormularioForm', () => {
const wrapper = mount(FormularioForm, {
propsData: { values: { name: 'Dave Barnett', candy: true } },
slots: { default: `
<FormularioInput v-slot="inputProps" name="name" validation="required" >
<input v-model="inputProps.context.model" type="text">
<FormularioInput v-slot="{ context }" name="name" validation="required">
<input v-model="context.model" type="text">
</FormularioInput>
` }
})
@ -288,12 +287,8 @@ describe('FormularioForm', () => {
const wrapper = mount({
template: `
<div>
<FormularioForm
name="login"
/>
<FormularioForm
name="register"
/>
<FormularioForm name="login" />
<FormularioForm name="register" />
</div>
`
})