refactor: moved isScalar to new file, improved typehinting
This commit is contained in:
parent
013931fbfc
commit
e6271bb069
@ -52,20 +52,20 @@ type Empty = null | undefined
|
|||||||
|
|
||||||
@Component({ name: 'FormularioField', inheritAttrs: false })
|
@Component({ name: 'FormularioField', inheritAttrs: false })
|
||||||
export default class FormularioField extends Vue {
|
export default class FormularioField extends Vue {
|
||||||
@Inject({ default: undefined }) formularioSetter!: Function|undefined
|
@Inject({ default: undefined }) __FormularioForm_set!: Function|undefined
|
||||||
@Inject({ default: () => (): void => {} }) onFormularioFieldValidation!: Function
|
@Inject({ default: () => (): void => {} }) __FormularioForm_emitValidation!: Function
|
||||||
@Inject({ default: undefined }) formularioRegister!: Function|undefined
|
@Inject({ default: undefined }) __FormularioForm_register!: Function|undefined
|
||||||
@Inject({ default: undefined }) formularioDeregister!: Function|undefined
|
@Inject({ default: undefined }) __FormularioForm_unregister!: Function|undefined
|
||||||
@Inject({ default: () => (): Record<string, any> => ({}) }) getFormValues!: Function
|
@Inject({ default: () => (): Record<string, unknown> => ({}) }) __FormularioForm_getValue!: () => Record<string, unknown>
|
||||||
@Inject({ default: undefined }) addErrorObserver!: Function|undefined
|
@Inject({ default: undefined }) __FormularioForm_addErrorObserver!: Function|undefined
|
||||||
@Inject({ default: undefined }) removeErrorObserver!: Function|undefined
|
@Inject({ default: undefined }) __FormularioForm_removeErrorObserver!: Function|undefined
|
||||||
@Inject({ default: '' }) path!: string
|
@Inject({ default: '' }) path!: string
|
||||||
|
|
||||||
@Model('input', { default: '' }) value!: any
|
@Model('input', { default: '' }) value!: unknown
|
||||||
|
|
||||||
@Prop({
|
@Prop({
|
||||||
required: true,
|
required: true,
|
||||||
validator: (name: any): boolean => typeof name === 'string' && name.length > 0,
|
validator: (name: unknown): boolean => typeof name === 'string' && name.length > 0,
|
||||||
}) name!: string
|
}) name!: string
|
||||||
|
|
||||||
@Prop({ default: '' }) validation!: string|any[]
|
@Prop({ default: '' }) validation!: string|any[]
|
||||||
@ -82,22 +82,24 @@ export default class FormularioField extends Vue {
|
|||||||
@Prop({ default: () => <U, T>(value: U|Empty): U|T|Empty => value }) modelGetConverter!: ModelGetConverter
|
@Prop({ default: () => <U, T>(value: U|Empty): U|T|Empty => value }) modelGetConverter!: ModelGetConverter
|
||||||
@Prop({ default: () => <T, U>(value: U|T): U|T => value }) modelSetConverter!: ModelSetConverter
|
@Prop({ default: () => <T, U>(value: U|T): U|T => value }) modelSetConverter!: ModelSetConverter
|
||||||
|
|
||||||
public proxy: any = this.getInitialValue()
|
public proxy: unknown = this.getInitialValue()
|
||||||
|
|
||||||
private localErrors: string[] = []
|
private localErrors: string[] = []
|
||||||
private violations: Violation[] = []
|
|
||||||
private validationRun: Promise<any> = Promise.resolve()
|
|
||||||
|
|
||||||
get fullQualifiedName (): string {
|
private violations: Violation[] = []
|
||||||
|
|
||||||
|
private validationRun: Promise<Violation[]> = Promise.resolve([])
|
||||||
|
|
||||||
|
private get fullQualifiedName (): string {
|
||||||
return this.path !== '' ? `${this.path}.${this.name}` : this.name
|
return this.path !== '' ? `${this.path}.${this.name}` : this.name
|
||||||
}
|
}
|
||||||
|
|
||||||
get model (): any {
|
private get model (): unknown {
|
||||||
const model = this.hasModel ? 'value' : 'proxy'
|
const model = this.hasModel ? 'value' : 'proxy'
|
||||||
return this.modelGetConverter(this[model])
|
return this.modelGetConverter(this[model])
|
||||||
}
|
}
|
||||||
|
|
||||||
set model (value: any) {
|
private set model (value: unknown) {
|
||||||
value = this.modelSetConverter(value, this.proxy)
|
value = this.modelSetConverter(value, this.proxy)
|
||||||
|
|
||||||
if (!shallowEqualObjects(value, this.proxy)) {
|
if (!shallowEqualObjects(value, this.proxy)) {
|
||||||
@ -106,12 +108,12 @@ export default class FormularioField extends Vue {
|
|||||||
|
|
||||||
this.$emit('input', value)
|
this.$emit('input', value)
|
||||||
|
|
||||||
if (typeof this.formularioSetter === 'function') {
|
if (typeof this.__FormularioForm_set === 'function') {
|
||||||
this.formularioSetter(this.context.name, value)
|
this.__FormularioForm_set(this.context.name, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get context (): Context<any> {
|
private get context (): Context<unknown> {
|
||||||
return Object.defineProperty({
|
return Object.defineProperty({
|
||||||
name: this.fullQualifiedName,
|
name: this.fullQualifiedName,
|
||||||
runValidation: this.runValidation.bind(this),
|
runValidation: this.runValidation.bind(this),
|
||||||
@ -120,13 +122,13 @@ export default class FormularioField extends Vue {
|
|||||||
allErrors: [...this.localErrors, ...this.violations.map(v => v.message)],
|
allErrors: [...this.localErrors, ...this.violations.map(v => v.message)],
|
||||||
}, 'model', {
|
}, 'model', {
|
||||||
get: () => this.model,
|
get: () => this.model,
|
||||||
set: (value: any) => {
|
set: (value: unknown) => {
|
||||||
this.model = value
|
this.model = value
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get normalizedValidationRules (): Record<string, ValidationRuleFn> {
|
private get normalizedValidationRules (): Record<string, ValidationRuleFn> {
|
||||||
const rules: Record<string, ValidationRuleFn> = {}
|
const rules: Record<string, ValidationRuleFn> = {}
|
||||||
Object.keys(this.validationRules).forEach(key => {
|
Object.keys(this.validationRules).forEach(key => {
|
||||||
rules[snakeToCamel(key)] = this.validationRules[key]
|
rules[snakeToCamel(key)] = this.validationRules[key]
|
||||||
@ -134,7 +136,7 @@ export default class FormularioField extends Vue {
|
|||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
get normalizedValidationMessages (): Record<string, ValidationMessageI18NFn|string> {
|
private get normalizedValidationMessages (): Record<string, ValidationMessageI18NFn|string> {
|
||||||
const messages: Record<string, ValidationMessageI18NFn|string> = {}
|
const messages: Record<string, ValidationMessageI18NFn|string> = {}
|
||||||
Object.keys(this.validationMessages).forEach(key => {
|
Object.keys(this.validationMessages).forEach(key => {
|
||||||
messages[snakeToCamel(key)] = this.validationMessages[key]
|
messages[snakeToCamel(key)] = this.validationMessages[key]
|
||||||
@ -145,12 +147,12 @@ export default class FormularioField extends Vue {
|
|||||||
/**
|
/**
|
||||||
* Determines if this formulario element is v-modeled or not.
|
* Determines if this formulario element is v-modeled or not.
|
||||||
*/
|
*/
|
||||||
get hasModel (): boolean {
|
private get hasModel (): boolean {
|
||||||
return has(this.$options.propsData || {}, 'value')
|
return has(this.$options.propsData || {}, 'value')
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch('proxy')
|
@Watch('proxy')
|
||||||
onProxyChanged (newValue: any, oldValue: any): void {
|
private onProxyChange (newValue: unknown, oldValue: unknown): void {
|
||||||
if (!this.hasModel && !shallowEqualObjects(newValue, oldValue)) {
|
if (!this.hasModel && !shallowEqualObjects(newValue, oldValue)) {
|
||||||
this.context.model = newValue
|
this.context.model = newValue
|
||||||
}
|
}
|
||||||
@ -162,7 +164,7 @@ export default class FormularioField extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
onValueChanged (newValue: any, oldValue: any): void {
|
private onValueChange (newValue: unknown, oldValue: unknown): void {
|
||||||
if (this.hasModel && !shallowEqualObjects(newValue, oldValue)) {
|
if (this.hasModel && !shallowEqualObjects(newValue, oldValue)) {
|
||||||
this.context.model = newValue
|
this.context.model = newValue
|
||||||
}
|
}
|
||||||
@ -170,32 +172,31 @@ export default class FormularioField extends Vue {
|
|||||||
|
|
||||||
created (): void {
|
created (): void {
|
||||||
this.initProxy()
|
this.initProxy()
|
||||||
if (typeof this.formularioRegister === 'function') {
|
if (typeof this.__FormularioForm_register === 'function') {
|
||||||
this.formularioRegister(this.fullQualifiedName, this)
|
this.__FormularioForm_register(this.fullQualifiedName, this)
|
||||||
}
|
}
|
||||||
if (typeof this.addErrorObserver === 'function' && !this.errorsDisabled) {
|
if (typeof this.__FormularioForm_addErrorObserver === 'function' && !this.errorsDisabled) {
|
||||||
this.addErrorObserver({ callback: this.setErrors, type: 'input', field: this.fullQualifiedName })
|
this.__FormularioForm_addErrorObserver({ callback: this.setErrors, type: 'field', field: this.fullQualifiedName })
|
||||||
}
|
}
|
||||||
if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
||||||
this.runValidation()
|
this.runValidation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
beforeDestroy (): void {
|
beforeDestroy (): void {
|
||||||
if (!this.errorsDisabled && typeof this.removeErrorObserver === 'function') {
|
if (!this.errorsDisabled && typeof this.__FormularioForm_removeErrorObserver === 'function') {
|
||||||
this.removeErrorObserver(this.setErrors)
|
this.__FormularioForm_removeErrorObserver(this.setErrors)
|
||||||
}
|
}
|
||||||
if (typeof this.formularioDeregister === 'function') {
|
if (typeof this.__FormularioForm_unregister === 'function') {
|
||||||
this.formularioDeregister(this.fullQualifiedName)
|
this.__FormularioForm_unregister(this.fullQualifiedName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getInitialValue (): any {
|
private getInitialValue (): unknown {
|
||||||
return has(this.$options.propsData || {}, 'value') ? this.value : ''
|
return has(this.$options.propsData || {}, 'value') ? this.value : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
initProxy (): void {
|
private initProxy (): void {
|
||||||
// This should only be run immediately on created and ensures that the
|
// This should only be run immediately on created and ensures that the
|
||||||
// proxy and the model are both the same before any additional registration.
|
// proxy and the model are both the same before any additional registration.
|
||||||
if (!shallowEqualObjects(this.context.model, this.proxy)) {
|
if (!shallowEqualObjects(this.context.model, this.proxy)) {
|
||||||
@ -213,8 +214,8 @@ export default class FormularioField extends Vue {
|
|||||||
violations: this.violations,
|
violations: this.violations,
|
||||||
}
|
}
|
||||||
this.$emit('validation', payload)
|
this.$emit('validation', payload)
|
||||||
if (typeof this.onFormularioFieldValidation === 'function') {
|
if (typeof this.__FormularioForm_emitValidation === 'function') {
|
||||||
this.onFormularioFieldValidation(payload)
|
this.__FormularioForm_emitValidation(payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +232,7 @@ export default class FormularioField extends Vue {
|
|||||||
), {
|
), {
|
||||||
value: this.context.model,
|
value: this.context.model,
|
||||||
name: this.context.name,
|
name: this.context.name,
|
||||||
formValues: this.getFormValues(),
|
formValues: this.__FormularioForm_getValue(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,24 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { Component, Model, Prop, Provide, Watch } from 'vue-property-decorator'
|
import {
|
||||||
import { clone, getNested, has, merge, setNested, shallowEqualObjects } from '@/utils'
|
Component,
|
||||||
|
Model,
|
||||||
|
Prop,
|
||||||
|
Provide,
|
||||||
|
Watch,
|
||||||
|
} from 'vue-property-decorator'
|
||||||
|
import {
|
||||||
|
clone,
|
||||||
|
getNested,
|
||||||
|
has,
|
||||||
|
merge,
|
||||||
|
setNested,
|
||||||
|
shallowEqualObjects,
|
||||||
|
} from '@/utils'
|
||||||
|
|
||||||
import Registry from '@/form/registry'
|
import Registry from '@/form/registry'
|
||||||
|
|
||||||
import FormularioField from '@/FormularioField.vue'
|
import FormularioField from '@/FormularioField.vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -19,20 +34,25 @@ import {
|
|||||||
|
|
||||||
import { Violation } from '@/validation/validator'
|
import { Violation } from '@/validation/validator'
|
||||||
|
|
||||||
|
type ValidationEventPayload = {
|
||||||
|
name: string;
|
||||||
|
violations: Violation[];
|
||||||
|
}
|
||||||
|
|
||||||
@Component({ name: 'FormularioForm' })
|
@Component({ name: 'FormularioForm' })
|
||||||
export default class FormularioForm extends Vue {
|
export default class FormularioForm extends Vue {
|
||||||
@Model('input', { default: () => ({}) })
|
@Model('input', { default: () => ({}) })
|
||||||
public readonly formularioValue!: Record<string, any>
|
public readonly formularioValue!: Record<string, unknown>
|
||||||
|
|
||||||
// Errors record, describing state validation errors of whole form
|
// Errors record, describing state validation errors of whole form
|
||||||
@Prop({ default: () => ({}) }) readonly errors!: Record<string, any>
|
@Prop({ default: () => ({}) }) readonly errors!: Record<string, string[]>
|
||||||
// Form errors only used on FormularioForm default slot
|
// Form errors only used on FormularioForm default slot
|
||||||
@Prop({ default: () => ([]) }) readonly formErrors!: string[]
|
@Prop({ default: () => ([]) }) readonly formErrors!: string[]
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
public path = ''
|
public path = ''
|
||||||
|
|
||||||
public proxy: Record<string, any> = {}
|
public proxy: Record<string, unknown> = {}
|
||||||
|
|
||||||
private registry: Registry = new Registry(this)
|
private registry: Registry = new Registry(this)
|
||||||
|
|
||||||
@ -41,7 +61,7 @@ export default class FormularioForm extends Vue {
|
|||||||
private localFormErrors: string[] = []
|
private localFormErrors: string[] = []
|
||||||
private localFieldErrors: Record<string, string[]> = {}
|
private localFieldErrors: Record<string, string[]> = {}
|
||||||
|
|
||||||
get initialValues (): Record<string, any> {
|
get initialValues (): Record<string, unknown> {
|
||||||
if (this.hasModel && typeof this.formularioValue === 'object') {
|
if (this.hasModel && typeof this.formularioValue === 'object') {
|
||||||
// If there is a v-model on the form/group, use those values as first priority
|
// If there is a v-model on the form/group, use those values as first priority
|
||||||
return { ...this.formularioValue } // @todo - use a deep clone to detach reference types
|
return { ...this.formularioValue } // @todo - use a deep clone to detach reference types
|
||||||
@ -67,7 +87,7 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('formularioValue', { deep: true })
|
@Watch('formularioValue', { deep: true })
|
||||||
onFormularioValueChanged (values: Record<string, any>): void {
|
onFormularioValueChanged (values: Record<string, unknown>): void {
|
||||||
if (this.hasModel && values && typeof values === 'object') {
|
if (this.hasModel && values && typeof values === 'object') {
|
||||||
this.setValues(values)
|
this.setValues(values)
|
||||||
}
|
}
|
||||||
@ -80,7 +100,7 @@ export default class FormularioForm extends Vue {
|
|||||||
|
|
||||||
@Watch('mergedFieldErrors', { deep: true, immediate: true })
|
@Watch('mergedFieldErrors', { deep: true, immediate: true })
|
||||||
onMergedFieldErrorsChanged (errors: Record<string, string[]>): void {
|
onMergedFieldErrorsChanged (errors: Record<string, string[]>): void {
|
||||||
this.errorObserverRegistry.filter(o => o.type === 'input').observe(errors)
|
this.errorObserverRegistry.filter(o => o.type === 'field').observe(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
created (): void {
|
created (): void {
|
||||||
@ -88,7 +108,7 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
getFormValues (): Record<string, any> {
|
getFormValues (): Record<string, unknown> {
|
||||||
return this.proxy
|
return this.proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,12 +124,12 @@ export default class FormularioForm extends Vue {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide()
|
@Provide('__FormularioForm_emitValidation')
|
||||||
onFormularioFieldValidation (payload: { name: string; violations: Violation[]}): void {
|
onFormularioFieldValidation (payload: ValidationEventPayload): void {
|
||||||
this.$emit('validation', payload)
|
this.$emit('validation', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide()
|
@Provide('__FormularioForm_addErrorObserver')
|
||||||
addErrorObserver (observer: ErrorObserver): void {
|
addErrorObserver (observer: ErrorObserver): void {
|
||||||
this.errorObserverRegistry.add(observer)
|
this.errorObserverRegistry.add(observer)
|
||||||
if (observer.type === 'form') {
|
if (observer.type === 'form') {
|
||||||
@ -119,18 +139,18 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide()
|
@Provide('__FormularioForm_removeErrorObserver')
|
||||||
removeErrorObserver (observer: ErrorHandler): void {
|
removeErrorObserver (observer: ErrorHandler): void {
|
||||||
this.errorObserverRegistry.remove(observer)
|
this.errorObserverRegistry.remove(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide('formularioRegister')
|
@Provide('__FormularioForm_register')
|
||||||
register (field: string, component: FormularioField): void {
|
private register (field: string, component: FormularioField): void {
|
||||||
this.registry.add(field, component)
|
this.registry.add(field, component)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide('formularioDeregister')
|
@Provide('__FormularioForm_unregister')
|
||||||
deregister (field: string): void {
|
private unregister (field: string): void {
|
||||||
this.registry.remove(field)
|
this.registry.remove(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +160,7 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValues (values: Record<string, any>): void {
|
setValues (values: Record<string, unknown>): void {
|
||||||
const keys = Array.from(new Set([...Object.keys(values), ...Object.keys(this.proxy)]))
|
const keys = Array.from(new Set([...Object.keys(values), ...Object.keys(this.proxy)]))
|
||||||
let proxyHasChanges = false
|
let proxyHasChanges = false
|
||||||
keys.forEach(field => {
|
keys.forEach(field => {
|
||||||
@ -148,18 +168,19 @@ export default class FormularioForm extends Vue {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.getNested(field).forEach((registryField, registryKey) => {
|
this.registry.getNested(field).forEach((_, fqn) => {
|
||||||
const $input = this.registry.get(registryKey) as FormularioField
|
const $field = this.registry.get(fqn) as FormularioField
|
||||||
const oldValue = getNested(this.proxy, registryKey)
|
|
||||||
const newValue = getNested(values, registryKey)
|
const oldValue = getNested(this.proxy, fqn)
|
||||||
|
const newValue = getNested(values, fqn)
|
||||||
|
|
||||||
if (!shallowEqualObjects(newValue, oldValue)) {
|
if (!shallowEqualObjects(newValue, oldValue)) {
|
||||||
this.setFieldValue(registryKey, newValue)
|
this.setFieldValue(fqn, newValue)
|
||||||
proxyHasChanges = true
|
proxyHasChanges = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shallowEqualObjects(newValue, $input.proxy)) {
|
if (!shallowEqualObjects(newValue, $field.proxy)) {
|
||||||
$input.context.model = newValue
|
$field.context.model = newValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -171,7 +192,7 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFieldValue (field: string, value: any): void {
|
setFieldValue (field: string, value: unknown): void {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { [field]: value, ...proxy } = this.proxy
|
const { [field]: value, ...proxy } = this.proxy
|
||||||
@ -181,8 +202,8 @@ export default class FormularioForm extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provide('formularioSetter')
|
@Provide('__FormularioForm_set')
|
||||||
setFieldValueAndEmit (field: string, value: any): void {
|
setFieldValueAndEmit (field: string, value: unknown): void {
|
||||||
this.setFieldValue(field, value)
|
this.setFieldValue(field, value)
|
||||||
this.$emit('input', { ...this.proxy })
|
this.$emit('input', { ...this.proxy })
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* If given parameter is not string, object ot array, result will be an empty array.
|
* If given parameter is not string, object ot array, result will be an empty array.
|
||||||
* @param {*} item
|
* @param {*} item
|
||||||
*/
|
*/
|
||||||
export default function arrayify (item: any): any[] {
|
export default function arrayify (item: unknown): unknown[] {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ export default function arrayify (item: any): any[] {
|
|||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
if (typeof item === 'object') {
|
if (typeof item === 'object') {
|
||||||
return Object.values(item)
|
return Object.values(item as Record<string, unknown>)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,32 @@
|
|||||||
import isScalar from '@/utils/isScalar'
|
|
||||||
import has from '@/utils/has'
|
import has from '@/utils/has'
|
||||||
|
import { RecordLike, Scalar, isScalar } from '@/utils/types'
|
||||||
|
|
||||||
|
type Cloneable = Scalar|Date|RecordLike<unknown>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple (somewhat non-comprehensive) clone function, valid for our use
|
* A simple (somewhat non-comprehensive) clone function, valid for our use
|
||||||
* case of needing to unbind reactive watchers.
|
* case of needing to unbind reactive watchers.
|
||||||
*/
|
*/
|
||||||
export default function clone (value: any): any {
|
export default function clone (value: Cloneable): Cloneable {
|
||||||
if (typeof value !== 'object') {
|
if (isScalar(value)) {
|
||||||
return value
|
return value as Scalar
|
||||||
}
|
}
|
||||||
|
|
||||||
const copy: any | Record<string, any> = Array.isArray(value) ? [] : {}
|
if (value instanceof Date) {
|
||||||
|
return new Date(value)
|
||||||
|
}
|
||||||
|
|
||||||
for (const key in value) {
|
const source: RecordLike<unknown> = value as RecordLike<unknown>
|
||||||
if (has(value, key)) {
|
const copy: RecordLike<unknown> = Array.isArray(source) ? [] : {}
|
||||||
if (isScalar(value[key])) {
|
|
||||||
copy[key] = value[key]
|
for (const key in source) {
|
||||||
} else if (value instanceof Date) {
|
if (has(source, key)) {
|
||||||
copy[key] = new Date(copy[key])
|
if (isScalar(source[key])) {
|
||||||
|
copy[key] = source[key]
|
||||||
|
} else if (source[key] instanceof Date) {
|
||||||
|
copy[key] = new Date(source[key] as Date)
|
||||||
} else {
|
} else {
|
||||||
copy[key] = clone(value[key])
|
copy[key] = clone(source[key] as Cloneable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export { default as arrayify } from './arrayify'
|
export { default as arrayify } from './arrayify'
|
||||||
export { default as clone } from './clone'
|
export { default as clone } from './clone'
|
||||||
export { default as has } from './has'
|
export { default as has } from './has'
|
||||||
export { default as isScalar } from './isScalar'
|
export { isScalar } from './types'
|
||||||
export { default as merge } from './merge'
|
export { default as merge } from './merge'
|
||||||
export { default as regexForFormat } from './regexForFormat'
|
export { default as regexForFormat } from './regexForFormat'
|
||||||
export { default as shallowEqualObjects } from './shallowEqualObjects'
|
export { default as shallowEqualObjects } from './shallowEqualObjects'
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
export default function isScalar (data: any): boolean {
|
|
||||||
switch (typeof data) {
|
|
||||||
case 'symbol':
|
|
||||||
case 'number':
|
|
||||||
case 'string':
|
|
||||||
case 'boolean':
|
|
||||||
case 'undefined':
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return data === null
|
|
||||||
}
|
|
||||||
}
|
|
17
src/utils/types.ts
Normal file
17
src/utils/types.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export type RecordKey = string | number
|
||||||
|
export type RecordLike<T> = T[] | Record<RecordKey, T>
|
||||||
|
|
||||||
|
export type Scalar = boolean | number | string | symbol | undefined | null
|
||||||
|
|
||||||
|
export function isScalar (value: unknown): boolean {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
case 'symbol':
|
||||||
|
case 'undefined':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return value === null
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,12 @@ export interface ErrorHandler {
|
|||||||
|
|
||||||
export interface ErrorObserver {
|
export interface ErrorObserver {
|
||||||
callback: ErrorHandler;
|
callback: ErrorHandler;
|
||||||
type: 'form' | 'field';
|
type: ErrorObserverType;
|
||||||
field?: string;
|
field?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ErrorObserverType = 'form' | 'field'
|
||||||
|
|
||||||
export interface ErrorObserverPredicate {
|
export interface ErrorObserverPredicate {
|
||||||
(value: ErrorObserver, index: number, array: ErrorObserver[]): unknown;
|
(value: ErrorObserver, index: number, array: ErrorObserver[]): unknown;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import isScalar from '@/utils/isScalar'
|
|
||||||
|
|
||||||
describe('isScalar', () => {
|
|
||||||
it('Passes on strings', () => expect(isScalar('hello')).toBe(true))
|
|
||||||
|
|
||||||
it('Passes on numbers', () => expect(isScalar(123)).toBe(true))
|
|
||||||
|
|
||||||
it('Passes on booleans', () => expect(isScalar(false)).toBe(true))
|
|
||||||
|
|
||||||
it('Passes on symbols', () => expect(isScalar(Symbol(123))).toBe(true))
|
|
||||||
|
|
||||||
it('Passes on null', () => expect(isScalar(null)).toBe(true))
|
|
||||||
|
|
||||||
it('Passes on undefined', () => expect(isScalar(undefined)).toBe(true))
|
|
||||||
|
|
||||||
it('Fails on pojo', () => expect(isScalar({})).toBe(false))
|
|
||||||
})
|
|
14
test/unit/utils/types.test.js
Normal file
14
test/unit/utils/types.test.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { isScalar } from '@/utils/types'
|
||||||
|
|
||||||
|
describe('isScalar', () => {
|
||||||
|
const expectIsScalar = value => expect(isScalar(value)).toBe(true)
|
||||||
|
|
||||||
|
test('passes on booleans', () => expectIsScalar(false))
|
||||||
|
test('passes on numbers', () => expectIsScalar(123))
|
||||||
|
test('passes on strings', () => expectIsScalar('hello'))
|
||||||
|
test('passes on symbols', () => expectIsScalar(Symbol(123)))
|
||||||
|
test('passes on undefined', () => expectIsScalar(undefined))
|
||||||
|
test('passes on null', () => expectIsScalar(null))
|
||||||
|
|
||||||
|
test('fails on pojo', () => expect(isScalar({})).toBe(false))
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user