vuetify 2.5.13 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/json/attributes.json +188 -0
  2. package/dist/json/tags.json +86 -20
  3. package/dist/json/web-types.json +697 -139
  4. package/dist/vuetify.css +136 -5
  5. package/dist/vuetify.css.map +1 -1
  6. package/dist/vuetify.js +927 -304
  7. package/dist/vuetify.js.map +1 -1
  8. package/dist/vuetify.min.css +2 -2
  9. package/dist/vuetify.min.js +2 -2
  10. package/es5/components/VAutocomplete/VAutocomplete.js +1 -0
  11. package/es5/components/VAutocomplete/VAutocomplete.js.map +1 -1
  12. package/es5/components/VBanner/VBanner.js +3 -2
  13. package/es5/components/VBanner/VBanner.js.map +1 -1
  14. package/es5/components/VBottomSheet/VBottomSheet.js +1 -4
  15. package/es5/components/VBottomSheet/VBottomSheet.js.map +1 -1
  16. package/es5/components/VCalendar/VCalendar.js +2 -2
  17. package/es5/components/VCalendar/VCalendar.js.map +1 -1
  18. package/es5/components/VCalendar/VCalendarDaily.js +16 -8
  19. package/es5/components/VCalendar/VCalendarDaily.js.map +1 -1
  20. package/es5/components/VCalendar/VCalendarWeekly.js +14 -10
  21. package/es5/components/VCalendar/VCalendarWeekly.js.map +1 -1
  22. package/es5/components/VCalendar/mixins/calendar-with-events.js +19 -20
  23. package/es5/components/VCalendar/mixins/calendar-with-events.js.map +1 -1
  24. package/es5/components/VCalendar/mixins/mouse.js +13 -4
  25. package/es5/components/VCalendar/mixins/mouse.js.map +1 -1
  26. package/es5/components/VDataTable/MobileRow.js +2 -2
  27. package/es5/components/VDataTable/MobileRow.js.map +1 -1
  28. package/es5/components/VDataTable/Row.js +25 -9
  29. package/es5/components/VDataTable/Row.js.map +1 -1
  30. package/es5/components/VDataTable/VDataTableHeaderDesktop.js +1 -1
  31. package/es5/components/VDataTable/VDataTableHeaderDesktop.js.map +1 -1
  32. package/es5/components/VDatePicker/mixins/date-picker-table.js +13 -3
  33. package/es5/components/VDatePicker/mixins/date-picker-table.js.map +1 -1
  34. package/es5/components/VDialog/VDialog.js +4 -10
  35. package/es5/components/VDialog/VDialog.js.map +1 -1
  36. package/es5/components/VInput/VInput.js +3 -1
  37. package/es5/components/VInput/VInput.js.map +1 -1
  38. package/es5/components/VItemGroup/VItemGroup.js +8 -4
  39. package/es5/components/VItemGroup/VItemGroup.js.map +1 -1
  40. package/es5/components/VMenu/VMenu.js +1 -1
  41. package/es5/components/VMenu/VMenu.js.map +1 -1
  42. package/es5/components/VOtpInput/VOtpInput.js +375 -0
  43. package/es5/components/VOtpInput/VOtpInput.js.map +1 -0
  44. package/es5/components/VOtpInput/index.js +20 -0
  45. package/es5/components/VOtpInput/index.js.map +1 -0
  46. package/es5/components/VRadioGroup/VRadioGroup.js +1 -3
  47. package/es5/components/VRadioGroup/VRadioGroup.js.map +1 -1
  48. package/es5/components/VSlideGroup/VSlideGroup.js +8 -1
  49. package/es5/components/VSlideGroup/VSlideGroup.js.map +1 -1
  50. package/es5/components/VSnackbar/VSnackbar.js +4 -8
  51. package/es5/components/VSnackbar/VSnackbar.js.map +1 -1
  52. package/es5/components/VTabs/VTab.js +3 -2
  53. package/es5/components/VTabs/VTab.js.map +1 -1
  54. package/es5/components/VTextField/VTextField.js +6 -1
  55. package/es5/components/VTextField/VTextField.js.map +1 -1
  56. package/es5/components/VTooltip/VTooltip.js +0 -1
  57. package/es5/components/VTooltip/VTooltip.js.map +1 -1
  58. package/es5/components/VTreeview/VTreeview.js +2 -1
  59. package/es5/components/VTreeview/VTreeview.js.map +1 -1
  60. package/es5/components/VTreeview/VTreeviewNode.js +3 -1
  61. package/es5/components/VTreeview/VTreeviewNode.js.map +1 -1
  62. package/es5/components/index.js +13 -0
  63. package/es5/components/index.js.map +1 -1
  64. package/es5/directives/click-outside/index.js +3 -1
  65. package/es5/directives/click-outside/index.js.map +1 -1
  66. package/es5/directives/intersect/index.js +8 -5
  67. package/es5/directives/intersect/index.js.map +1 -1
  68. package/es5/directives/mutate/index.js +3 -2
  69. package/es5/directives/mutate/index.js.map +1 -1
  70. package/es5/directives/resize/index.js +3 -1
  71. package/es5/directives/resize/index.js.map +1 -1
  72. package/es5/directives/ripple/index.js +0 -7
  73. package/es5/directives/ripple/index.js.map +1 -1
  74. package/es5/directives/scroll/index.js.map +1 -1
  75. package/es5/framework.js +1 -1
  76. package/es5/locale/da.js +77 -0
  77. package/es5/locale/da.js.map +1 -0
  78. package/es5/locale/index.js +8 -0
  79. package/es5/locale/index.js.map +1 -1
  80. package/es5/locale/it.js +6 -6
  81. package/es5/locale/it.js.map +1 -1
  82. package/es5/mixins/intersectable/index.js +6 -0
  83. package/es5/mixins/intersectable/index.js.map +1 -1
  84. package/es5/mixins/routable/index.js +2 -5
  85. package/es5/mixins/routable/index.js.map +1 -1
  86. package/es5/util/helpers.js +13 -6
  87. package/es5/util/helpers.js.map +1 -1
  88. package/lib/components/VAutocomplete/VAutocomplete.js +1 -0
  89. package/lib/components/VAutocomplete/VAutocomplete.js.map +1 -1
  90. package/lib/components/VBanner/VBanner.js +3 -2
  91. package/lib/components/VBanner/VBanner.js.map +1 -1
  92. package/lib/components/VBottomSheet/VBottomSheet.js +1 -4
  93. package/lib/components/VBottomSheet/VBottomSheet.js.map +1 -1
  94. package/lib/components/VCalendar/VCalendar.js +2 -2
  95. package/lib/components/VCalendar/VCalendar.js.map +1 -1
  96. package/lib/components/VCalendar/VCalendarDaily.js +20 -8
  97. package/lib/components/VCalendar/VCalendarDaily.js.map +1 -1
  98. package/lib/components/VCalendar/VCalendarWeekly.js +10 -2
  99. package/lib/components/VCalendar/VCalendarWeekly.js.map +1 -1
  100. package/lib/components/VCalendar/mixins/calendar-with-events.js +6 -3
  101. package/lib/components/VCalendar/mixins/calendar-with-events.js.map +1 -1
  102. package/lib/components/VCalendar/mixins/mouse.js +9 -4
  103. package/lib/components/VCalendar/mixins/mouse.js.map +1 -1
  104. package/lib/components/VDataTable/MobileRow.js +2 -2
  105. package/lib/components/VDataTable/MobileRow.js.map +1 -1
  106. package/lib/components/VDataTable/Row.js +15 -8
  107. package/lib/components/VDataTable/Row.js.map +1 -1
  108. package/lib/components/VDataTable/VDataTableHeaderDesktop.js +1 -1
  109. package/lib/components/VDataTable/VDataTableHeaderDesktop.js.map +1 -1
  110. package/lib/components/VDatePicker/mixins/date-picker-table.js +12 -2
  111. package/lib/components/VDatePicker/mixins/date-picker-table.js.map +1 -1
  112. package/lib/components/VDialog/VDialog.js +4 -10
  113. package/lib/components/VDialog/VDialog.js.map +1 -1
  114. package/lib/components/VInput/VInput.js +2 -0
  115. package/lib/components/VInput/VInput.js.map +1 -1
  116. package/lib/components/VItemGroup/VItemGroup.js +7 -5
  117. package/lib/components/VItemGroup/VItemGroup.js.map +1 -1
  118. package/lib/components/VMenu/VMenu.js +1 -1
  119. package/lib/components/VMenu/VMenu.js.map +1 -1
  120. package/lib/components/VOtpInput/VOtpInput.js +342 -0
  121. package/lib/components/VOtpInput/VOtpInput.js.map +1 -0
  122. package/lib/components/VOtpInput/index.js +4 -0
  123. package/lib/components/VOtpInput/index.js.map +1 -0
  124. package/lib/components/VRadioGroup/VRadioGroup.js +2 -4
  125. package/lib/components/VRadioGroup/VRadioGroup.js.map +1 -1
  126. package/lib/components/VSlideGroup/VSlideGroup.js +9 -1
  127. package/lib/components/VSlideGroup/VSlideGroup.js.map +1 -1
  128. package/lib/components/VSnackbar/VSnackbar.js +4 -8
  129. package/lib/components/VSnackbar/VSnackbar.js.map +1 -1
  130. package/lib/components/VTabs/VTab.js +3 -2
  131. package/lib/components/VTabs/VTab.js.map +1 -1
  132. package/lib/components/VTextField/VTextField.js +8 -1
  133. package/lib/components/VTextField/VTextField.js.map +1 -1
  134. package/lib/components/VTooltip/VTooltip.js +0 -1
  135. package/lib/components/VTooltip/VTooltip.js.map +1 -1
  136. package/lib/components/VTreeview/VTreeview.js +2 -1
  137. package/lib/components/VTreeview/VTreeview.js.map +1 -1
  138. package/lib/components/VTreeview/VTreeviewNode.js +3 -1
  139. package/lib/components/VTreeview/VTreeviewNode.js.map +1 -1
  140. package/lib/components/index.js +1 -0
  141. package/lib/components/index.js.map +1 -1
  142. package/lib/directives/click-outside/index.js +3 -1
  143. package/lib/directives/click-outside/index.js.map +1 -1
  144. package/lib/directives/intersect/index.js +7 -5
  145. package/lib/directives/intersect/index.js.map +1 -1
  146. package/lib/directives/mutate/index.js +3 -2
  147. package/lib/directives/mutate/index.js.map +1 -1
  148. package/lib/directives/resize/index.js +3 -1
  149. package/lib/directives/resize/index.js.map +1 -1
  150. package/lib/directives/ripple/index.js +0 -7
  151. package/lib/directives/ripple/index.js.map +1 -1
  152. package/lib/directives/scroll/index.js.map +1 -1
  153. package/lib/framework.js +1 -1
  154. package/lib/locale/da.js +70 -0
  155. package/lib/locale/da.js.map +1 -0
  156. package/lib/locale/index.js +1 -0
  157. package/lib/locale/index.js.map +1 -1
  158. package/lib/locale/it.js +6 -6
  159. package/lib/locale/it.js.map +1 -1
  160. package/lib/mixins/intersectable/index.js +4 -0
  161. package/lib/mixins/intersectable/index.js.map +1 -1
  162. package/lib/mixins/routable/index.js +3 -5
  163. package/lib/mixins/routable/index.js.map +1 -1
  164. package/lib/util/helpers.js +13 -6
  165. package/lib/util/helpers.js.map +1 -1
  166. package/package.json +2 -2
  167. package/src/components/VAutocomplete/VAutocomplete.ts +2 -0
  168. package/src/components/VAutocomplete/__tests__/VAutocomplete3.spec.ts +16 -0
  169. package/src/components/VBanner/VBanner.ts +16 -10
  170. package/src/components/VBottomSheet/VBottomSheet.ts +1 -4
  171. package/src/components/VCalendar/VCalendar.ts +3 -2
  172. package/src/components/VCalendar/VCalendarCategory.sass +67 -0
  173. package/src/components/VCalendar/VCalendarDaily.ts +9 -8
  174. package/src/components/VCalendar/VCalendarWeekly.ts +4 -2
  175. package/src/components/VCalendar/_variables.scss +4 -0
  176. package/src/components/VCalendar/mixins/calendar-with-events.ts +4 -3
  177. package/src/components/VCalendar/mixins/mouse.ts +10 -4
  178. package/src/components/VDataTable/MobileRow.ts +2 -2
  179. package/src/components/VDataTable/Row.ts +23 -16
  180. package/src/components/VDataTable/VDataTableHeaderDesktop.ts +1 -1
  181. package/src/components/VDatePicker/__tests__/VDatePicker.date.spec.ts +16 -0
  182. package/src/components/VDatePicker/__tests__/VDatePicker.month.spec.ts +16 -0
  183. package/src/components/VDatePicker/mixins/date-picker-table.ts +24 -2
  184. package/src/components/VDialog/VDialog.ts +4 -10
  185. package/src/components/VInput/VInput.sass +8 -0
  186. package/src/components/VInput/VInput.ts +2 -0
  187. package/src/components/VItemGroup/VItemGroup.ts +5 -3
  188. package/src/components/VItemGroup/__tests__/VItemGroup.spec.ts +19 -2
  189. package/src/components/VMenu/VMenu.ts +1 -1
  190. package/src/components/VOtpInput/VOtpInput.sass +37 -0
  191. package/src/components/VOtpInput/VOtpInput.ts +322 -0
  192. package/src/components/VOtpInput/__tests__/VOtpInput.spec.ts +294 -0
  193. package/src/components/VOtpInput/_variables.scss +4 -0
  194. package/src/components/VOtpInput/index.ts +4 -0
  195. package/src/components/VRadioGroup/VRadioGroup.ts +0 -4
  196. package/src/components/VSlideGroup/VSlideGroup.ts +14 -1
  197. package/src/components/VSnackbar/VSnackbar.ts +3 -7
  198. package/src/components/VSnackbar/__tests__/VSnackbar.spec.ts +4 -1
  199. package/src/components/VStepper/VStepper.sass +4 -4
  200. package/src/components/VTabs/VTab.ts +10 -2
  201. package/src/components/VTextField/VTextField.ts +8 -3
  202. package/src/components/VTooltip/VTooltip.sass +4 -1
  203. package/src/components/VTooltip/VTooltip.ts +0 -1
  204. package/src/components/VTooltip/_variables.scss +1 -0
  205. package/src/components/VTreeview/VTreeview.ts +2 -1
  206. package/src/components/VTreeview/VTreeviewNode.ts +3 -1
  207. package/src/components/index.ts +1 -0
  208. package/src/directives/click-outside/index.ts +2 -2
  209. package/src/directives/intersect/index.ts +6 -5
  210. package/src/directives/mutate/index.ts +2 -3
  211. package/src/directives/resize/index.ts +2 -2
  212. package/src/directives/ripple/VRipple.sass +4 -1
  213. package/src/directives/ripple/index.ts +0 -7
  214. package/src/directives/scroll/index.ts +1 -1
  215. package/src/globals.d.ts +5 -5
  216. package/src/locale/da.ts +69 -0
  217. package/src/locale/index.ts +1 -0
  218. package/src/locale/it.ts +6 -6
  219. package/src/mixins/intersectable/index.ts +6 -0
  220. package/src/mixins/routable/index.ts +1 -4
  221. package/src/styles/settings/_variables.scss +4 -3
  222. package/src/util/__tests__/__snapshots__/helpers.spec.ts.snap +11 -0
  223. package/src/util/__tests__/helpers.spec.ts +17 -0
  224. package/src/util/helpers.ts +14 -7
  225. package/src/directives/ripple/_variables.scss +0 -6
@@ -0,0 +1,322 @@
1
+ // Styles
2
+ import '../VTextField/VTextField.sass'
3
+ import './VOtpInput.sass'
4
+
5
+ // Extensions
6
+ import VInput from '../VInput'
7
+ import VTextField from '../VTextField/VTextField'
8
+ // Directives
9
+ import ripple from '../../directives/ripple'
10
+
11
+ // Utilities
12
+ import { convertToUnit, keyCodes } from '../../util/helpers'
13
+ import { breaking } from '../../util/console'
14
+
15
+ // Types
16
+ import mixins from '../../util/mixins'
17
+ import { VNode } from 'vue'
18
+
19
+ const baseMixins = mixins(
20
+ VInput,
21
+ )
22
+
23
+ interface options extends InstanceType<typeof baseMixins> {
24
+ $refs: {
25
+ input: HTMLInputElement[]
26
+ }
27
+ }
28
+
29
+ /* @vue/component */
30
+ export default baseMixins.extend<options>().extend({
31
+ name: 'v-otp-input',
32
+
33
+ directives: {
34
+ ripple,
35
+ },
36
+
37
+ inheritAttrs: false,
38
+
39
+ props: {
40
+ length: {
41
+ type: [Number, String],
42
+ default: 6,
43
+ },
44
+ type: {
45
+ type: String,
46
+ default: 'text',
47
+ },
48
+ plain: Boolean,
49
+ },
50
+
51
+ data: () => ({
52
+ badInput: false,
53
+ initialValue: null,
54
+ isBooted: false,
55
+ otp: [] as string[],
56
+ }),
57
+
58
+ computed: {
59
+ outlined (): Boolean {
60
+ return !this.plain
61
+ },
62
+ classes (): object {
63
+ return {
64
+ ...VInput.options.computed.classes.call(this),
65
+ ...VTextField.options.computed.classes.call(this),
66
+ 'v-otp-input--plain': this.plain,
67
+ }
68
+ },
69
+ isDirty (): boolean {
70
+ return VInput.options.computed.isDirty.call(this) || this.badInput
71
+ },
72
+ },
73
+
74
+ watch: {
75
+ isFocused: 'updateValue',
76
+ value (val) {
77
+ this.lazyValue = val
78
+ },
79
+ },
80
+
81
+ created () {
82
+ /* istanbul ignore next */
83
+ if (this.$attrs.hasOwnProperty('browser-autocomplete')) {
84
+ breaking('browser-autocomplete', 'autocomplete', this)
85
+ }
86
+
87
+ this.otp = this.internalValue?.split('') || []
88
+ },
89
+
90
+ mounted () {
91
+ requestAnimationFrame(() => (this.isBooted = true))
92
+ },
93
+
94
+ methods: {
95
+ /** @public */
96
+ focus (e: Event, otpIdx: number) {
97
+ this.onFocus(e, otpIdx || 0)
98
+ },
99
+ genInputSlot (otpIdx: number) {
100
+ return this.$createElement('div', this.setBackgroundColor(this.backgroundColor, {
101
+ staticClass: 'v-input__slot',
102
+ style: { height: convertToUnit(this.height) },
103
+ on: {
104
+ click: () => this.onClick(otpIdx),
105
+ mousedown: (e: Event) => this.onMouseDown(e, otpIdx),
106
+ mouseup: (e: Event) => this.onMouseUp(e, otpIdx),
107
+ },
108
+ }), [this.genDefaultSlot(otpIdx)])
109
+ },
110
+ genControl (otpIdx: number) {
111
+ return this.$createElement('div', {
112
+ staticClass: 'v-input__control',
113
+ }, [
114
+ this.genInputSlot(otpIdx),
115
+ ])
116
+ },
117
+ genDefaultSlot (otpIdx: number) {
118
+ return [
119
+ this.genFieldset(),
120
+ this.genTextFieldSlot(otpIdx),
121
+ ]
122
+ },
123
+ genContent () {
124
+ return Array.from({ length: +this.length }, (_, i) => {
125
+ return this.$createElement('div', this.setTextColor(this.validationState, {
126
+ staticClass: 'v-input',
127
+ class: this.classes,
128
+ }), [this.genControl(i)])
129
+ })
130
+ },
131
+ genFieldset () {
132
+ return this.$createElement('fieldset', {
133
+ attrs: {
134
+ 'aria-hidden': true,
135
+ },
136
+ }, [this.genLegend()])
137
+ },
138
+ genLegend () {
139
+ const span = this.$createElement('span', {
140
+ domProps: { innerHTML: '&#8203;' },
141
+ })
142
+
143
+ return this.$createElement('legend', {
144
+ style: {
145
+ width: '0px',
146
+ },
147
+ }, [span])
148
+ },
149
+ genInput (otpIdx: number) {
150
+ const listeners = Object.assign({}, this.listeners$)
151
+ delete listeners.change // Change should not be bound externally
152
+
153
+ return this.$createElement('input', {
154
+ style: {},
155
+ domProps: {
156
+ value: this.otp[otpIdx],
157
+ min: this.type === 'number' ? 0 : null,
158
+ },
159
+ attrs: {
160
+ ...this.attrs$,
161
+ disabled: this.isDisabled,
162
+ readonly: this.isReadonly,
163
+ type: this.type,
164
+ id: `${this.computedId}--${otpIdx}`,
165
+ class: `otp-field-box--${otpIdx}`,
166
+ maxlength: 1,
167
+ },
168
+ on: Object.assign(listeners, {
169
+ blur: this.onBlur,
170
+ input: (e: Event) => this.onInput(e, otpIdx),
171
+ focus: (e: Event) => this.onFocus(e, otpIdx),
172
+ paste: (e: ClipboardEvent) => this.onPaste(e, otpIdx),
173
+ keydown: this.onKeyDown,
174
+ keyup: (e: KeyboardEvent) => this.onKeyUp(e, otpIdx),
175
+ }),
176
+ ref: 'input',
177
+ refInFor: true,
178
+ })
179
+ },
180
+ genTextFieldSlot (otpIdx: number): VNode {
181
+ return this.$createElement('div', {
182
+ staticClass: 'v-text-field__slot',
183
+ }, [
184
+ this.genInput(otpIdx),
185
+ ])
186
+ },
187
+ onBlur (e?: Event) {
188
+ this.isFocused = false
189
+ e && this.$nextTick(() => this.$emit('blur', e))
190
+ },
191
+ onClick (otpIdx: number) {
192
+ if (this.isFocused || this.isDisabled || !this.$refs.input[otpIdx]) return
193
+
194
+ this.onFocus(undefined, otpIdx)
195
+ },
196
+ onFocus (e?: Event, otpIdx?: number) {
197
+ e?.preventDefault()
198
+ e?.stopPropagation()
199
+ const elements = this.$refs.input as HTMLInputElement[]
200
+ const ref = this.$refs.input && elements[otpIdx || 0]
201
+ if (!ref) return
202
+
203
+ if (document.activeElement !== ref) {
204
+ ref.focus()
205
+ return ref.select()
206
+ }
207
+
208
+ if (!this.isFocused) {
209
+ this.isFocused = true
210
+ ref.select()
211
+ e && this.$emit('focus', e)
212
+ }
213
+ },
214
+ onInput (e: Event, otpIdx: number) {
215
+ const target = e.target as HTMLInputElement
216
+ const value = target.value
217
+ this.applyValue(otpIdx, target.value, () => {
218
+ this.internalValue = this.otp.join('')
219
+ })
220
+ this.badInput = target.validity && target.validity.badInput
221
+
222
+ const nextIndex = otpIdx + 1
223
+ if (value) {
224
+ if (nextIndex < +this.length) {
225
+ this.changeFocus(nextIndex)
226
+ } else {
227
+ this.clearFocus(otpIdx)
228
+ this.onCompleted()
229
+ }
230
+ }
231
+ },
232
+ clearFocus (index: number) {
233
+ const input = this.$refs.input[index] as HTMLInputElement
234
+ input.blur()
235
+ },
236
+ onKeyDown (e: KeyboardEvent) {
237
+ if (e.keyCode === keyCodes.enter) {
238
+ this.$emit('change', this.internalValue)
239
+ }
240
+
241
+ this.$emit('keydown', e)
242
+ },
243
+ onMouseDown (e: Event, otpIdx: number) {
244
+ // Prevent input from being blurred
245
+ if (e.target !== this.$refs.input[otpIdx]) {
246
+ e.preventDefault()
247
+ e.stopPropagation()
248
+ }
249
+
250
+ VInput.options.methods.onMouseDown.call(this, e)
251
+ },
252
+ onMouseUp (e: Event, otpIdx: number) {
253
+ if (this.hasMouseDown) this.focus(e, otpIdx)
254
+
255
+ VInput.options.methods.onMouseUp.call(this, e)
256
+ },
257
+ onPaste (event: ClipboardEvent, index: number) {
258
+ const maxCursor = +this.length - 1
259
+ const inputVal = event?.clipboardData?.getData('Text')
260
+ const inputDataArray = inputVal?.split('') || []
261
+ event.preventDefault()
262
+ const newOtp: string[] = [...this.otp]
263
+ for (let i = 0; i < inputDataArray.length; i++) {
264
+ const appIdx = index + i
265
+ if (appIdx > maxCursor) break
266
+ newOtp[appIdx] = inputDataArray[i].toString()
267
+ }
268
+ this.otp = newOtp
269
+ const targetFocus = Math.min(index + inputDataArray.length, maxCursor)
270
+ this.changeFocus(targetFocus)
271
+
272
+ if (newOtp.length === +this.length) { this.onCompleted(); this.clearFocus(targetFocus) }
273
+ },
274
+ applyValue (index: number, inputVal: string, next: Function) {
275
+ const newOtp: string[] = [...this.otp]
276
+ newOtp[index] = inputVal
277
+ this.otp = newOtp
278
+ next()
279
+ },
280
+ changeFocus (index: number) {
281
+ this.onFocus(undefined, index || 0)
282
+ },
283
+ updateValue (val: boolean) {
284
+ // Sets validationState from validatable
285
+ this.hasColor = val
286
+
287
+ if (val) {
288
+ this.initialValue = this.lazyValue
289
+ } else if (this.initialValue !== this.lazyValue) {
290
+ this.$emit('change', this.lazyValue)
291
+ }
292
+ },
293
+ onKeyUp (event: KeyboardEvent, index: number) {
294
+ event.preventDefault()
295
+ const eventKey = event.key
296
+ if (['Tab', 'Shift', 'Meta', 'Control', 'Alt'].includes(eventKey)) {
297
+ return
298
+ }
299
+ if (['Delete'].includes(eventKey)) {
300
+ return
301
+ }
302
+ if (eventKey === 'ArrowLeft' || (eventKey === 'Backspace' && !this.otp[index])) {
303
+ return index > 0 && this.changeFocus(index - 1)
304
+ }
305
+ if (eventKey === 'ArrowRight') {
306
+ return index + 1 < +this.length && this.changeFocus(index + 1)
307
+ }
308
+ },
309
+ onCompleted () {
310
+ const rsp = this.otp.join('')
311
+ if (rsp.length === +this.length) {
312
+ this.$emit('finish', rsp)
313
+ }
314
+ },
315
+ },
316
+ render (h): VNode {
317
+ return h('div', {
318
+ staticClass: 'v-otp-input',
319
+ class: this.themeClasses,
320
+ }, this.genContent())
321
+ },
322
+ })
@@ -0,0 +1,294 @@
1
+ import VOtpInput from '../VOtpInput'
2
+ import {
3
+ mount,
4
+ MountOptions,
5
+ Wrapper,
6
+ } from '@vue/test-utils'
7
+
8
+ describe('VOtpInput.ts', () => {
9
+ type Instance = InstanceType<typeof VOtpInput>
10
+ let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
11
+ beforeEach(() => {
12
+ mountFunction = (options?: MountOptions<Instance>) => {
13
+ return mount(VOtpInput, {
14
+ // https://github.com/vuejs/vue-test-utils/issues/1130
15
+ sync: false,
16
+ ...options,
17
+ })
18
+ }
19
+ })
20
+
21
+ it('should update lazyValue when value is updated', async () => {
22
+ const wrapper = mountFunction({
23
+ propsData: {
24
+ value: 'foo',
25
+ },
26
+ })
27
+
28
+ expect(wrapper.vm.lazyValue).toBe('foo')
29
+
30
+ wrapper.setProps({ value: 'bar' })
31
+ await wrapper.vm.$nextTick()
32
+
33
+ expect(wrapper.vm.lazyValue).toBe('bar')
34
+ })
35
+
36
+ it('should trigger focus events', async () => {
37
+ // const updateValue = jest.fn()
38
+ const wrapper = mountFunction(
39
+ {
40
+ propsData: {
41
+ type: 'number',
42
+ },
43
+ }
44
+ )
45
+
46
+ wrapper.vm.focus(null, 0)
47
+
48
+ const focus = jest.fn()
49
+ wrapper.vm.$on('focus', focus)
50
+
51
+ await wrapper.vm.$nextTick()
52
+
53
+ expect(wrapper.vm.isFocused).toBe(true)
54
+ wrapper.setProps({ value: 'foo' })
55
+ await wrapper.vm.$nextTick()
56
+ wrapper.setData({ isFocused: false })
57
+ wrapper.vm.onFocus()
58
+ })
59
+
60
+ it('should fire change event when pressing enter', async () => {
61
+ const wrapper = mountFunction()
62
+ const input = wrapper.findAll('input').at(0)
63
+ const element = input.element as HTMLInputElement
64
+ const change = jest.fn()
65
+
66
+ wrapper.vm.$on('change', change)
67
+
68
+ input.trigger('focus')
69
+ element.value = 'a'
70
+ input.trigger('input')
71
+ await wrapper.vm.$nextTick()
72
+ input.trigger('focus')
73
+ await wrapper.vm.$nextTick()
74
+ input.trigger('keydown.space')
75
+ input.trigger('keydown.enter')
76
+ input.trigger('keydown.enter')
77
+
78
+ expect(change).toHaveBeenCalledTimes(2)
79
+ })
80
+
81
+ it('should call the correct event for different click locations', () => {
82
+ const onClick = jest.fn()
83
+ const onMouseDown = jest.fn()
84
+ const onMouseUp = jest.fn()
85
+ const wrapper = mountFunction({
86
+ methods: {
87
+ onClick,
88
+ onMouseDown,
89
+ onMouseUp,
90
+ },
91
+ })
92
+
93
+ const slot = wrapper.findAll('.v-input__slot').at(0)
94
+
95
+ wrapper.trigger('click')
96
+ wrapper.trigger('mousedown')
97
+ wrapper.trigger('mouseup')
98
+ slot.trigger('click')
99
+ slot.trigger('mousedown')
100
+ slot.trigger('mouseup')
101
+
102
+ expect(onClick).toHaveBeenCalledTimes(1)
103
+ expect(onMouseDown).toHaveBeenCalledTimes(1)
104
+ expect(onMouseUp).toHaveBeenCalledTimes(1)
105
+ })
106
+
107
+ it('should call the correct event for different click locations 2', () => {
108
+ const onMouseDown = jest.fn()
109
+ const wrapper = mountFunction()
110
+
111
+ const slot = wrapper.findAll('.v-input__slot').at(0)
112
+ const input = slot.find('input')
113
+
114
+ wrapper.trigger('click')
115
+ wrapper.trigger('mousedown')
116
+ slot.trigger('click')
117
+ slot.trigger('mousedown')
118
+ slot.trigger('click')
119
+ input.trigger('mousedown')
120
+
121
+ expect(onMouseDown).toHaveBeenCalledTimes(0)
122
+ })
123
+
124
+ it('should not focus input when mousedown did not originate from input', () => {
125
+ const focus = jest.fn()
126
+ const wrapper = mountFunction({
127
+ methods: { focus },
128
+ })
129
+
130
+ const input = wrapper.findAll('.v-input__slot').at(0)
131
+ const element = input.find('input').element as HTMLInputElement
132
+ input.trigger('mousedown')
133
+ input.trigger('mouseup')
134
+ input.trigger('mouseup')
135
+
136
+ expect(wrapper.vm.$refs.input[0]).toBe(element)
137
+ expect(focus).toHaveBeenCalledTimes(1)
138
+ })
139
+
140
+ it('should pass events to internal input field', () => {
141
+ const keyup = jest.fn()
142
+ const component = {
143
+ render (h) {
144
+ return h(VOtpInput, { on: { keyUp: keyup }, props: { }, attrs: {} })
145
+ },
146
+ }
147
+ const wrapper = mount(component)
148
+
149
+ const input = wrapper.findAll('input').at(0)
150
+ input.trigger('keyUp', { keyCode: 65 })
151
+
152
+ expect(keyup).toHaveBeenCalled()
153
+ })
154
+
155
+ it('should fire event when pressing keyboard defined keys', () => {
156
+ const wrapper = mountFunction()
157
+ const input = wrapper.find('input')
158
+ const element = input.element as HTMLInputElement
159
+ const change = jest.fn()
160
+
161
+ wrapper.vm.$on('change', change)
162
+
163
+ input.trigger('focus')
164
+ element.value = '1'
165
+ input.trigger('input')
166
+ input.trigger('keydown.enter')
167
+ input.trigger('keydown.enter')
168
+ const keys = ['Tab', 'Shift', 'Meta', 'Control', 'Alt', 'Delete', 'ArrowRight', 'Backspace']
169
+ keys.forEach(key => {
170
+ input.trigger('keyup', { key })
171
+ })
172
+
173
+ expect(change).toHaveBeenCalledTimes(2)
174
+ })
175
+
176
+ it('should process input when paste event', async () => {
177
+ const getData = jest.fn(() => '1337078')
178
+ const event = {
179
+ clipboardData: {
180
+ getData,
181
+ },
182
+ }
183
+
184
+ const wrapper = mountFunction({})
185
+
186
+ const input = wrapper.findAll('input').at(0)
187
+
188
+ const element = input.element as HTMLInputElement
189
+ input.trigger('focus')
190
+ await wrapper.vm.$nextTick()
191
+ expect(document.activeElement === element).toBe(true)
192
+
193
+ input.trigger('paste', event)
194
+ await wrapper.vm.$nextTick()
195
+
196
+ expect(wrapper.vm.otp).toStrictEqual('133707'.split(''))
197
+ })
198
+
199
+ it('should process input when paste event with empty event', async () => {
200
+ const event = {}
201
+
202
+ const wrapper = mountFunction({})
203
+
204
+ const input = wrapper.findAll('input').at(0)
205
+
206
+ const element = input.element as HTMLInputElement
207
+ input.trigger('focus')
208
+ await wrapper.vm.$nextTick()
209
+ expect(document.activeElement === element).toBe(true)
210
+
211
+ input.trigger('paste', event)
212
+ await wrapper.vm.$nextTick()
213
+
214
+ expect(wrapper.vm.otp).toStrictEqual(''.split(''))
215
+ })
216
+
217
+ it('should clear cursor when input typing is done', async () => {
218
+ const onFinish = jest.fn()
219
+ const clearFocus = jest.fn()
220
+ const wrapper = mountFunction({
221
+ propsData: {
222
+ value: '',
223
+ length: 2,
224
+ },
225
+ methods: {
226
+ clearFocus,
227
+ },
228
+ })
229
+
230
+ wrapper.vm.$on('finish', onFinish)
231
+
232
+ const input = wrapper.findAll('input').at(0)
233
+ const input2 = wrapper.findAll('input').at(1)
234
+ const element = input.element as HTMLInputElement
235
+ const element2 = input2.element as HTMLInputElement
236
+
237
+ input.trigger('focus')
238
+ await wrapper.vm.$nextTick()
239
+ element.value = 'a'
240
+ input.trigger('input')
241
+ await wrapper.vm.$nextTick()
242
+ expect(document.activeElement === element2).toBe(true)
243
+
244
+ await wrapper.vm.$nextTick()
245
+ element2.value = 'b'
246
+ input2.trigger('input')
247
+ await wrapper.vm.$nextTick()
248
+
249
+ const onFocus = jest.spyOn(wrapper.vm.$refs.input[0], 'focus')
250
+ expect(onFocus).toHaveBeenCalledTimes(0)
251
+ expect(onFinish).toHaveBeenCalledTimes(1)
252
+ expect(clearFocus).toHaveBeenCalledTimes(1)
253
+ })
254
+
255
+ it('should run onComplete when input cursor reached end without full OTP value', async () => {
256
+ const wrapper = mountFunction({
257
+ propsData: {
258
+ value: '',
259
+ length: 2,
260
+ },
261
+ methods: {
262
+ },
263
+ })
264
+
265
+ const input = wrapper.findAll('input').at(1)
266
+ const element = input.element as HTMLInputElement
267
+ input.trigger('focus')
268
+ await wrapper.vm.$nextTick()
269
+ input.trigger('input')
270
+ await wrapper.vm.$nextTick()
271
+ element.value = 'b'
272
+ input.trigger('input')
273
+ await wrapper.vm.$nextTick()
274
+
275
+ const onFocus = jest.spyOn(wrapper.vm.$refs.input[0], 'focus')
276
+ expect(onFocus).toHaveBeenCalledTimes(0)
277
+ })
278
+
279
+ it('should change cursor left when input focus and keyup left', async () => {
280
+ const wrapper = mountFunction()
281
+
282
+ const input = wrapper.findAll('input').at(1)
283
+ const input2 = wrapper.findAll('input').at(0)
284
+ const element = input2.element as HTMLInputElement
285
+ input.trigger('focus')
286
+ await wrapper.vm.$nextTick()
287
+ input.trigger('keyup', {
288
+ key: 'ArrowLeft',
289
+ })
290
+ await wrapper.vm.$nextTick()
291
+
292
+ expect(document.activeElement === element).toBe(true)
293
+ })
294
+ })
@@ -0,0 +1,4 @@
1
+ @import '../../styles/styles.sass';
2
+
3
+ $otp-gutter: 4px !default;
4
+ $otp-width: 24px !default;
@@ -0,0 +1,4 @@
1
+ import VOtpInput from './VOtpInput'
2
+
3
+ export { VOtpInput }
4
+ export default VOtpInput
@@ -6,15 +6,11 @@ import './VRadioGroup.sass'
6
6
  import VInput from '../VInput'
7
7
  import { BaseItemGroup } from '../VItemGroup/VItemGroup'
8
8
 
9
- // Mixins
10
- import Comparable from '../../mixins/comparable'
11
-
12
9
  // Types
13
10
  import mixins from '../../util/mixins'
14
11
  import { PropType } from 'vue'
15
12
 
16
13
  const baseMixins = mixins(
17
- Comparable,
18
14
  BaseItemGroup,
19
15
  VInput
20
16
  )
@@ -42,6 +42,12 @@ interface options extends Vue {
42
42
  }
43
43
  }
44
44
 
45
+ function bias (val: number) {
46
+ const c = 0.501
47
+ const x = Math.abs(val)
48
+ return Math.sign(val) * (x / ((1 / c - 2) * (1 - x) + 1))
49
+ }
50
+
45
51
  export function calculateUpdatedOffset (
46
52
  selectedElement: HTMLElement,
47
53
  widths: Widths,
@@ -210,7 +216,14 @@ export const BaseSlideGroup = mixins<options &
210
216
  // and need to be recalculated
211
217
  isOverflowing: 'setWidths',
212
218
  scrollOffset (val) {
213
- this.$refs.content.style.transform = `translateX(${-val}px)`
219
+ const scroll =
220
+ val <= 0
221
+ ? bias(-val)
222
+ : val > this.widths.content - this.widths.wrapper
223
+ ? -(this.widths.content - this.widths.wrapper) + bias(this.widths.content - this.widths.wrapper - val)
224
+ : -val
225
+
226
+ this.$refs.content.style.transform = `translateX(${scroll}px)`
214
227
  },
215
228
  },
216
229
 
@@ -90,9 +90,7 @@ export default mixins(
90
90
  : Themeable.options.computed.isDark.call(this)
91
91
  },
92
92
  styles (): object {
93
- // Styles are not needed when
94
- // using the absolute prop.
95
- if (this.absolute) return {}
93
+ if (this.absolute || !this.app) return {}
96
94
 
97
95
  const {
98
96
  bar,
@@ -104,12 +102,10 @@ export default mixins(
104
102
  top,
105
103
  } = this.$vuetify.application
106
104
 
107
- // Should always move for y-axis
108
- // applicationable components.
109
105
  return {
110
106
  paddingBottom: convertToUnit(bottom + footer + insetFooter),
111
- paddingLeft: !this.app ? undefined : convertToUnit(left),
112
- paddingRight: !this.app ? undefined : convertToUnit(right),
107
+ paddingLeft: convertToUnit(left),
108
+ paddingRight: convertToUnit(right),
113
109
  paddingTop: convertToUnit(bar + top),
114
110
  }
115
111
  },
@@ -63,7 +63,10 @@ describe('VSnackbar.ts', () => {
63
63
  [true, false],
64
64
  ])('should have app padding on the x-axis using %s', (absolute, expected: boolean) => {
65
65
  const wrapper = mountFunction({
66
- propsData: { absolute },
66
+ propsData: {
67
+ app: true,
68
+ absolute,
69
+ },
67
70
  })
68
71
 
69
72
  expect(Object.keys(wrapper.vm.styles).length > 0).toBe(expected)