1
// Components
2
import VInput from '../VInput/VInput'
3

4
// Mixins
5 1
import mixins from '../../util/mixins'
6 1
import BindsAttrs from '../../mixins/binds-attrs'
7 1
import { provide as RegistrableProvide } from '../../mixins/registrable'
8

9
// Helpers
10
import { VNode } from 'vue'
11

12
type ErrorBag = Record<number, boolean>
13
type VInputInstance = InstanceType<typeof VInput>
14
type Watchers = {
15
  _uid: number
16
  valid: () => void
17
  shouldValidate: () => void
18
}
19

20
/* @vue/component */
21 1
export default mixins(
22
  BindsAttrs,
23
  RegistrableProvide('form')
24
  /* @vue/component */
25
).extend({
26
  name: 'v-form',
27

28 1
  provide (): object {
29 1
    return { form: this }
30
  },
31

32
  inheritAttrs: false,
33

34
  props: {
35
    disabled: Boolean,
36
    lazyValidation: Boolean,
37
    readonly: Boolean,
38
    value: Boolean,
39
  },
40

41 1
  data: () => ({
42
    inputs: [] as VInputInstance[],
43
    watchers: [] as Watchers[],
44
    errorBag: {} as ErrorBag,
45
  }),
46

47
  watch: {
48
    errorBag: {
49 1
      handler (val) {
50 1
        const errors = Object.values(val).includes(true)
51

52 1
        this.$emit('input', !errors)
53
      },
54
      deep: true,
55
      immediate: true,
56
    },
57
  },
58

59
  methods: {
60 1
    watchInput (input: any): Watchers {
61 1
      const watcher = (input: any): (() => void) => {
62 1
        return input.$watch('hasError', (val: boolean) => {
63 1
          this.$set(this.errorBag, input._uid, val)
64
        }, { immediate: true })
65
      }
66

67 1
      const watchers: Watchers = {
68
        _uid: input._uid,
69 0
        valid: () => {},
70 1
        shouldValidate: () => {},
71
      }
72

73 1
      if (this.lazyValidation) {
74
        // Only start watching inputs if we need to
75 1
        watchers.shouldValidate = input.$watch('shouldValidate', (val: boolean) => {
76 1
          if (!val) return
77

78
          // Only watch if we're not already doing it
79 1
          if (this.errorBag.hasOwnProperty(input._uid)) return
80

81 1
          watchers.valid = watcher(input)
82
        })
83
      } else {
84 1
        watchers.valid = watcher(input)
85
      }
86

87 1
      return watchers
88
    },
89
    /** @public */
90 1
    validate (): boolean {
91 1
      return this.inputs.filter(input => !input.validate(true)).length === 0
92
    },
93
    /** @public */
94 1
    reset (): void {
95 1
      this.inputs.forEach(input => input.reset())
96 1
      this.resetErrorBag()
97
    },
98 1
    resetErrorBag () {
99 1
      if (this.lazyValidation) {
100
        // Account for timeout in validatable
101 1
        setTimeout(() => {
102 1
          this.errorBag = {}
103
        }, 0)
104
      }
105
    },
106
    /** @public */
107 1
    resetValidation () {
108 1
      this.inputs.forEach(input => input.resetValidation())
109 1
      this.resetErrorBag()
110
    },
111 1
    register (input: VInputInstance) {
112 1
      this.inputs.push(input)
113 1
      this.watchers.push(this.watchInput(input))
114
    },
115 1
    unregister (input: VInputInstance) {
116 1
      const found = this.inputs.find(i => i._uid === input._uid)
117

118 1
      if (!found) return
119

120 1
      const unwatch = this.watchers.find(i => i._uid === found._uid)
121 1
      if (unwatch) {
122 1
        unwatch.valid()
123 1
        unwatch.shouldValidate()
124
      }
125

126 1
      this.watchers = this.watchers.filter(i => i._uid !== found._uid)
127 1
      this.inputs = this.inputs.filter(i => i._uid !== found._uid)
128 1
      this.$delete(this.errorBag, found._uid)
129
    },
130
  },
131

132 1
  render (h): VNode {
133 1
    return h('form', {
134
      staticClass: 'v-form',
135
      attrs: {
136
        novalidate: true,
137
        ...this.attrs$,
138
      },
139
      on: {
140 0
        submit: (e: Event) => this.$emit('submit', e),
141
      },
142
    }, this.$slots.default)
143
  },
144
})

Read our documentation on viewing source code .

Loading