1
+ import {
2
+ defineComponent ,
3
+ PropType ,
4
+ onMounted
5
+ } from '@vue/composition-api' ;
6
+ import classnames from '@utils/classnames' ;
7
+ import { format } from '@utils/number' ;
8
+ import { VLayout } from 'vuetify/lib' ;
9
+ import { FAssetAmountInput } from '@foxone/uikit/src/components/FAssetAmountInput' ;
10
+ import { FButton } from '@foxone/uikit/src/components/FButton' ;
11
+ import 'vue-slider-component/theme/default.css' ;
12
+ /* import types */
13
+ import type { CreateElement , VNode } from 'vue' ;
14
+ import type { MixinAsset } from '@foxone/uikit/src/components/FAssetAmountInput/types' ;
15
+
16
+ // avoid typescript error
17
+ const VueSlider = require ( 'vue-slider-component' ) ;
18
+
19
+ export interface INPUT_TIPS {
20
+ amount ?: number ;
21
+ amountSymbol ?: string ;
22
+ tipLeft ?: string ;
23
+ tipRight : string ;
24
+ }
25
+
26
+ export type ERROR = string | ( ( value : number | string ) => string | VNode ) | VNode ;
27
+
28
+ export default defineComponent ( {
29
+ name : 'AssetRangeInput' ,
30
+
31
+ model : {
32
+ prop : 'value' ,
33
+ event : 'change'
34
+ } ,
35
+
36
+ props : {
37
+ value : {
38
+ type : [ String , Number ] ,
39
+ default : ''
40
+ } ,
41
+ className : {
42
+ type : String ,
43
+ default : ''
44
+ } ,
45
+ min : {
46
+ type : Number ,
47
+ default : 0
48
+ } ,
49
+ max : {
50
+ type : Number ,
51
+ default : 0
52
+ } ,
53
+ color : {
54
+ type : String ,
55
+ default : 'primary'
56
+ } ,
57
+ disabledInput : {
58
+ type : Boolean ,
59
+ default : void 0
60
+ } ,
61
+ disabledSlider : {
62
+ type : Boolean ,
63
+ default : void 0
64
+ } ,
65
+ disabledBtn : {
66
+ type : Boolean ,
67
+ default : void 0
68
+ } ,
69
+ selectable : {
70
+ type : Boolean ,
71
+ default : true
72
+ } ,
73
+ border : {
74
+ type : Boolean ,
75
+ default : false
76
+ } ,
77
+ loading : {
78
+ type : Boolean ,
79
+ default : false
80
+ } ,
81
+ assets : {
82
+ type : Array as PropType < MixinAsset [ ] > ,
83
+ default : ( ) => [ ]
84
+ } ,
85
+ asset : {
86
+ type : Object as PropType < MixinAsset > ,
87
+ default : ( ) => ( { } )
88
+ } ,
89
+ inputTips : {
90
+ type : Object as PropType < INPUT_TIPS > ,
91
+ default : ( ) => ( { } )
92
+ } ,
93
+ error : {
94
+ type : [ String , Function ] as PropType < ERROR > ,
95
+ default : ''
96
+ } ,
97
+ ratioText : {
98
+ type : String ,
99
+ default : 'Ratio'
100
+ } ,
101
+ showSlider : {
102
+ type : Boolean ,
103
+ default : true
104
+ } ,
105
+ btnText : {
106
+ type : String ,
107
+ default : 'Confirm'
108
+ } ,
109
+ showBtn : {
110
+ type : Boolean ,
111
+ default : true
112
+ } ,
113
+ } ,
114
+
115
+ setup ( props ) {
116
+ const classes = classnames ( 'asset-range-input' ) ;
117
+
118
+ onMounted ( ( ) => {
119
+ console . info ( 'AssetRangeInput mounted!' ) ;
120
+ } ) ;
121
+
122
+ return {
123
+ classes
124
+ } ;
125
+ } ,
126
+
127
+ data : ( ) => {
128
+ const _value_input = '' as string | number ;
129
+ const _value_slider = 0 as string | number ;
130
+ return {
131
+ _value_input,
132
+ _value_slider
133
+ }
134
+ } ,
135
+
136
+ computed : {
137
+ bindAsset : {
138
+ get ( ) : MixinAsset {
139
+ return this . asset ;
140
+ } ,
141
+ set ( val ) {
142
+ this . $emit ( 'update:asset' , val ) ;
143
+ }
144
+ } ,
145
+ hasInputLeftTips ( ) : boolean {
146
+ return ! ! ( this . inputTips ?. amount || this . inputTips ?. amountSymbol || this . inputTips ?. tipLeft ) ;
147
+ } ,
148
+ hasUpperLimit ( ) : number {
149
+ return this . max && ( 1 / this . max ) ;
150
+ }
151
+ } ,
152
+
153
+ watch : {
154
+ value : function ( val : string ) {
155
+ if ( ! this . isInRange ( + val ) ) this . $emit ( 'error:limit' ) ;
156
+ this . _value_input = val ;
157
+ this . setSliderValueWithLimit ( + val ) ;
158
+ }
159
+ } ,
160
+
161
+ created ( ) {
162
+ if ( ! this . isInRange ( + this . value ) ) this . $emit ( 'error:limit' ) ;
163
+ this . _value_input = this . value ;
164
+ this . setSliderValueWithLimit ( + this . value ) ;
165
+ } ,
166
+
167
+ methods : {
168
+ isInRange ( val : number ) {
169
+ return val >= this . min && ( ! this . hasUpperLimit || val <= this . max ) ;
170
+ } ,
171
+ setSliderValueWithLimit ( val : number ) {
172
+ if ( this . hasUpperLimit && typeof val === 'number' ) {
173
+ let _val = val / this . max * 100 ;
174
+ if ( _val > 100 ) _val = 100 ;
175
+ if ( _val < 0 ) _val = 0 ;
176
+ this . _value_slider = format ( {
177
+ n : _val ,
178
+ p : 2
179
+ } ) ;
180
+ }
181
+ }
182
+ } ,
183
+
184
+ render ( h : CreateElement ) : VNode {
185
+ const btn = this . $slots . button ;
186
+ const slider = this . $slots . slider ;
187
+ const inputTips = this . $slots . inputTips ;
188
+ const sliderTips = this . $slots . sliderTips ;
189
+ const scopedSlots = {
190
+ activator : this . $scopedSlots . activator
191
+ } ;
192
+
193
+ return (
194
+ < VLayout
195
+ class = { this . classes ( void 0 , this . className ) }
196
+ attrs = { {
197
+ column : true ,
198
+ 'align-center' : true
199
+ } }
200
+ >
201
+ < div class = { this . classes ( 'top' ) } >
202
+ < FAssetAmountInput
203
+ vModel = { this . _value_input }
204
+ assets = { this . assets }
205
+ asset = { this . bindAsset }
206
+ selectable = { this . selectable }
207
+ border = { this . border }
208
+ loading = { this . loading }
209
+ vOn = { {
210
+ ...this . $listeners ,
211
+ input : ( val , e ) => {
212
+ if ( ! this . isInRange ( + val ) ) this . $emit ( 'error:limit' ) ;
213
+ this . _value_input = val ;
214
+ this . $emit ( 'input' , val ) ;
215
+ if ( ! this . value ) this . $nextTick ( ( ) => this . $forceUpdate ( ) ) ;
216
+ this . setSliderValueWithLimit ( + val ) ;
217
+ }
218
+ } }
219
+ scopedSlots = { scopedSlots }
220
+ />
221
+ {
222
+ inputTips ?? < VLayout
223
+ class = { this . classes ( 'top-tips' , 'mt-2 f-greyscale-3 f-caption' ) }
224
+ attrs = { {
225
+ 'justify-space-between' : true ,
226
+ 'align-center' : true
227
+ } }
228
+ >
229
+ {
230
+ this . hasInputLeftTips
231
+ ? < div class = { this . classes ( 'top-tips-left' ) } >
232
+ {
233
+ this . inputTips ?. tipLeft
234
+ ? < span class = { this . classes ( 'top-tips-left-txt' , 'mr-1' ) } >
235
+ { this . inputTips ?. tipLeft }
236
+ </ span >
237
+ : null
238
+ }
239
+ {
240
+ this . inputTips ?. amount
241
+ ? < span
242
+ class = { this . classes ( 'top-tips-left-amount' , 'f-greyscale-1 font-weight-bold' ) }
243
+ vOn :click = { ( ) => {
244
+ if ( ! this . isInRange ( + this . inputTips ?. amount ! ) ) this . $emit ( 'error:limit' ) ;
245
+ this . _value_input = `${ this . inputTips ?. amount ! } ` ;
246
+ this . setSliderValueWithLimit ( + this . _value_input ) ;
247
+ this . $emit ( 'input' , this . _value_input ) ;
248
+ this . $emit ( 'click:amount' ) ;
249
+ this . $nextTick ( ( ) => this . $forceUpdate ( ) ) ;
250
+ } }
251
+ >
252
+ { `${ this . inputTips ?. amount } ${ this . inputTips ?. amountSymbol ?? '' } ` }
253
+ </ span >
254
+ : null
255
+ }
256
+ </ div >
257
+ : null
258
+ }
259
+ {
260
+ this . inputTips ?. tipRight
261
+ ? < div class = { this . classes ( 'top-tips-right' ) } >
262
+ { this . inputTips . tipRight }
263
+ </ div >
264
+ : null
265
+ }
266
+ </ VLayout >
267
+ }
268
+ {
269
+ this . error
270
+ ? < div class = { this . classes ( 'top-error' , 'f-caption pt-2' ) } >
271
+ { typeof this . error === 'function' ? this . error ( this . _value_input ) : this . error }
272
+ </ div >
273
+ : < div class = { this . classes ( 'top-error-placeholder' ) } />
274
+ }
275
+ </ div >
276
+
277
+ {
278
+ this . showSlider
279
+ ? slider ?? < div class = { this . classes ( 'mid' , 'mb-8' ) } >
280
+ {
281
+ sliderTips ?? < VLayout
282
+ class = { this . classes ( 'mid-tips' , 'mb-5 f-greyscale-3 f-body-2' ) }
283
+ attrs = { {
284
+ 'justify-space-between' : true ,
285
+ 'align-center' : true
286
+ } }
287
+ >
288
+ < span > { this . ratioText } </ span >
289
+ < span class = 'f-greyscale-1 font-weight-bold' > { `${ this . _value_slider ?? 0 } %` } </ span >
290
+ </ VLayout >
291
+ }
292
+ < VueSlider
293
+ vModel = { this . _value_slider }
294
+ height = { 8 }
295
+ min = { 0 }
296
+ max = { 100 }
297
+ interval = { 0.01 }
298
+ lazy = { true }
299
+ tooltip = 'none'
300
+ contained = { true }
301
+ disabled = { this . disabledSlider }
302
+ vOn :change = {
303
+ val => {
304
+ this . _value_slider = + val ;
305
+ if ( this . hasUpperLimit ) {
306
+ this . _value_input = format ( {
307
+ n : + val / 100 * this . max ,
308
+ p : 8
309
+ } ) ;
310
+ this . $emit ( 'input' , this . _value_input ) ;
311
+ }
312
+ this . $nextTick ( ( ) => this . $forceUpdate ( ) ) ;
313
+ }
314
+ }
315
+ />
316
+ </ div >
317
+ : null
318
+ }
319
+
320
+ {
321
+ this . showBtn
322
+ ? btn ?? < FButton
323
+ class = 'px-8 py-4'
324
+ color = { this . color }
325
+ attrs = { {
326
+ disabled : this . disabledBtn !== void 0 ? this . disabledBtn : ! this . isInRange ( + this . _value_input )
327
+ } }
328
+ vOn :click = { ( ) => this . $emit ( 'click:button' ) }
329
+ >
330
+ { this . btnText }
331
+ </ FButton >
332
+ : null
333
+ }
334
+ </ VLayout >
335
+ ) ;
336
+ }
337
+ } ) ;
0 commit comments