@mixd-id/web-scaffold 0.1.230406001
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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/package.json +71 -0
- package/public/images/mixd-logo2.png +0 -0
- package/src/App.vue +17 -0
- package/src/components/Ahref.vue +34 -0
- package/src/components/Alert.vue +160 -0
- package/src/components/Button.vue +253 -0
- package/src/components/ButtonGroup.vue +101 -0
- package/src/components/Carousel.vue +293 -0
- package/src/components/ChatTyping.vue +69 -0
- package/src/components/Checkbox.vue +152 -0
- package/src/components/ContextMenu.vue +261 -0
- package/src/components/CopyToClipboard.vue +59 -0
- package/src/components/Countdown.vue +213 -0
- package/src/components/Datepicker.vue +312 -0
- package/src/components/Dropdown.vue +198 -0
- package/src/components/DynamicTemplate.vue +44 -0
- package/src/components/ErrorText.vue +36 -0
- package/src/components/Feed.vue +118 -0
- package/src/components/Gmaps.vue +227 -0
- package/src/components/Grid.vue +29 -0
- package/src/components/GridColumn.vue +31 -0
- package/src/components/HTMLEditor.vue +396 -0
- package/src/components/Image.vue +207 -0
- package/src/components/Image360.vue +140 -0
- package/src/components/ImageFullScreen.vue +101 -0
- package/src/components/ImagePreview.vue +71 -0
- package/src/components/ImportModal.vue +247 -0
- package/src/components/ListItem.vue +147 -0
- package/src/components/ListPage1.vue +1331 -0
- package/src/components/ListPage1Filter.vue +170 -0
- package/src/components/Modal.vue +253 -0
- package/src/components/OTPField.vue +126 -0
- package/src/components/Radio.vue +134 -0
- package/src/components/SearchButton.vue +57 -0
- package/src/components/Slider.vue +285 -0
- package/src/components/SplitPane.vue +129 -0
- package/src/components/Switch.vue +89 -0
- package/src/components/TabView.vue +106 -0
- package/src/components/TableView.vue +201 -0
- package/src/components/TableViewHead.vue +159 -0
- package/src/components/Tabs.vue +74 -0
- package/src/components/TextEditor.vue +85 -0
- package/src/components/Textarea.vue +184 -0
- package/src/components/Textbox.vue +200 -0
- package/src/components/Timepicker.vue +108 -0
- package/src/components/Toast.vue +93 -0
- package/src/components/VirtualScroll.vue +215 -0
- package/src/components/VirtualTable.vue +497 -0
- package/src/entry-client.js +27 -0
- package/src/entry-server.js +73 -0
- package/src/index.css +3 -0
- package/src/index.js +255 -0
- package/src/main.js +38 -0
- package/src/router.js +57 -0
- package/src/themes/default/index.js +200 -0
- package/src/utils/helpers.js +185 -0
- package/src/utils/helpers.mjs +197 -0
- package/src/utils/importer.js +156 -0
- package/src/utils/listpage1.js +1371 -0
- package/src/utils/selection.js +64 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="computedClass">
|
|
3
|
+
<slot name="start"></slot>
|
|
4
|
+
<input :type="type" :disabled="isDisabled" @focus="isActive = true" @blur="onBlur"
|
|
5
|
+
:placeholder="placeholder" :maxlength="maxlength" ref="input" autocomplete="new-password"
|
|
6
|
+
:value="modelValue ?? value" @input="onInput" :readonly="Boolean(readonly)"
|
|
7
|
+
@keydown="onKeyDown"/>
|
|
8
|
+
<div v-if="!!(errors)">
|
|
9
|
+
<svg :class="$style.svg" width="24" height="24" class="fill-red-500" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
10
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 13.75C12.4142 13.75 12.75 13.4142 12.75 13V8.00001C12.75 7.5858 12.4142 7.25001 12 7.25001C11.5858 7.25001 11.25 7.5858 11.25 8.00001V13C11.25 13.4142 11.5858 13.75 12 13.75Z"/>
|
|
11
|
+
<path d="M13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15C12.5523 15 13 15.4477 13 16Z"/>
|
|
12
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20.5C16.6944 20.5 20.5 16.6944 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/>
|
|
13
|
+
</svg>
|
|
14
|
+
</div>
|
|
15
|
+
<button class="mr-2" v-if="[ '1', 1, true, 'true' ].includes(clearable) && state >= 1 && modelValue" type="button" @click="$emit('clear')">
|
|
16
|
+
<svg :class="$style.svg" width="19" height="19" viewBox="0 0 24 24" class="fill-text-200" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
<path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
|
|
18
|
+
</svg>
|
|
19
|
+
</button>
|
|
20
|
+
<slot name="end"></slot>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
|
|
26
|
+
export default{
|
|
27
|
+
|
|
28
|
+
emits: [ 'update:modelValue', 'change', 'clear', 'submit', 'blur' ],
|
|
29
|
+
|
|
30
|
+
props: {
|
|
31
|
+
|
|
32
|
+
align: {
|
|
33
|
+
type: String
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
clearable: [ Boolean, String, Number ],
|
|
37
|
+
|
|
38
|
+
customClass: String,
|
|
39
|
+
|
|
40
|
+
state: {
|
|
41
|
+
type: Number,
|
|
42
|
+
default: 1 // 1:normal, -1:disabled, -2:error
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
variant: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: ''
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
size: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: ''
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
errors:{
|
|
56
|
+
type: [ String, Array ],
|
|
57
|
+
default: ''
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
modelValue: [ String, Number ],
|
|
61
|
+
|
|
62
|
+
value: String,
|
|
63
|
+
|
|
64
|
+
placeholder: [ String, Number ],
|
|
65
|
+
readonly: undefined,
|
|
66
|
+
maxlength: String,
|
|
67
|
+
type: String,
|
|
68
|
+
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
watch:{
|
|
72
|
+
|
|
73
|
+
modelValue(to, from){
|
|
74
|
+
this.$emit('update:modelValue', to)
|
|
75
|
+
this.$emit('change', to)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
computed: {
|
|
81
|
+
|
|
82
|
+
computedState(){
|
|
83
|
+
if(this.errors){
|
|
84
|
+
return -2
|
|
85
|
+
}
|
|
86
|
+
else{
|
|
87
|
+
return this.state
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
computedClass(){
|
|
92
|
+
return this.customClass ?? [
|
|
93
|
+
this.$style.textbox,
|
|
94
|
+
this.$style['state-' + this.computedState],
|
|
95
|
+
this.isActive && !this.readonly ? this.$style.active : '',
|
|
96
|
+
this.align ? this.$style['align-' + this.align] : '',
|
|
97
|
+
this.size ? this.$style['size-' + this.size] : ''
|
|
98
|
+
].join(' ')
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
isDisabled(){
|
|
102
|
+
return this.state === 2
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
mounted() {
|
|
108
|
+
this.$refs.input.addEventListener('keyup', this.onKeyUp)
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
data(){
|
|
112
|
+
return {
|
|
113
|
+
isActive: false
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
methods:{
|
|
118
|
+
|
|
119
|
+
select(){
|
|
120
|
+
this.$refs.input.select()
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
onKeyUp(e){
|
|
124
|
+
if(e.keyCode === 13){
|
|
125
|
+
this.$emit('submit')
|
|
126
|
+
}
|
|
127
|
+
else if(e.keyCode === 27 && this.clearable){
|
|
128
|
+
this.$emit('clear')
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
onInput(e){
|
|
133
|
+
this.$emit('update:modelValue', e.target.value)
|
|
134
|
+
this.$emit('change', e.target.value)
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
onBlur(e){
|
|
138
|
+
this.isActive = false
|
|
139
|
+
this.$emit('blur', e.target.value)
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
onKeyDown(e){
|
|
143
|
+
if([ 'number', 'tel' ].includes(this.type)){
|
|
144
|
+
if(((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105) ||
|
|
145
|
+
[ 8, 13 ].includes(e.keyCode)) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey);
|
|
146
|
+
else if(e.keyCode === 65 && (e.ctrlKey || e.metaKey));
|
|
147
|
+
else{
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<style module>
|
|
160
|
+
|
|
161
|
+
.textbox{
|
|
162
|
+
@apply min-h-[var(--h-cp)];
|
|
163
|
+
@apply flex items-center border-[1px] border-text-200 bg-base-50 rounded-lg overflow-hidden;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.textbox input:read-only{
|
|
167
|
+
@apply bg-text-50;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.size-sm{ @apply min-h-[var(--h-cp-sm)]; }
|
|
171
|
+
.size-sm input{ @apply text-sm; }
|
|
172
|
+
|
|
173
|
+
.size-lg{ @apply min-h-[var(--h-cp-lg)]; }
|
|
174
|
+
|
|
175
|
+
.textbox>input{
|
|
176
|
+
@apply flex-1 outline-none p-2 bg-transparent
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.active{
|
|
180
|
+
@apply border-primary
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.state--2{
|
|
184
|
+
@apply border-red-500
|
|
185
|
+
}
|
|
186
|
+
.state--2 .svg{
|
|
187
|
+
@apply fill-red-500
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.align-left>input{
|
|
191
|
+
@apply text-left
|
|
192
|
+
}
|
|
193
|
+
.align-center>input{
|
|
194
|
+
@apply text-center
|
|
195
|
+
}
|
|
196
|
+
.align-right>input{
|
|
197
|
+
@apply text-right
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
</style>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="computedClass">
|
|
3
|
+
<div>
|
|
4
|
+
<slot></slot>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="flex-1">
|
|
7
|
+
<select @change="onChanged" ref="hour">
|
|
8
|
+
<option v-for="_hour in 24" :value="(_hour - 1).toString().padStart(2, '0')"
|
|
9
|
+
:selected="(_hour - 1) === computedHour">
|
|
10
|
+
{{ (_hour - 1).toString().padStart(2, '0') }}
|
|
11
|
+
</option>
|
|
12
|
+
</select>
|
|
13
|
+
</div>
|
|
14
|
+
<div>
|
|
15
|
+
<label>:</label>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="flex-1">
|
|
18
|
+
<select @change="onChanged" ref="minute">
|
|
19
|
+
<option v-for="_minute in 60" :value="(_minute - 1).toString().padStart(2, '0')"
|
|
20
|
+
:selected="(_minute - 1) === computedMinute">
|
|
21
|
+
{{ (_minute - 1).toString().padStart(2, '0') }}
|
|
22
|
+
</option>
|
|
23
|
+
</select>
|
|
24
|
+
</div>
|
|
25
|
+
</span>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
export default{
|
|
30
|
+
|
|
31
|
+
props:{
|
|
32
|
+
|
|
33
|
+
modelValue: {
|
|
34
|
+
default: '00:00'
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
size:{
|
|
38
|
+
type: String,
|
|
39
|
+
default: ''
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
computed:{
|
|
45
|
+
|
|
46
|
+
computedClass(){
|
|
47
|
+
return [
|
|
48
|
+
'timepicker',
|
|
49
|
+
this.size !== '' ? 'timepicker-' + this.size : ''
|
|
50
|
+
]
|
|
51
|
+
.join(' ').trim()
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
computedHour(){
|
|
55
|
+
return parseInt(this.modelValue.split(':')[0])
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
computedMinute(){
|
|
59
|
+
return parseInt(this.modelValue.split(':')[1])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
emits: [ "update:modelValue" ],
|
|
65
|
+
|
|
66
|
+
methods:{
|
|
67
|
+
|
|
68
|
+
onChanged(){
|
|
69
|
+
const time = this.$refs.hour.value + ':' + this.$refs.minute.value
|
|
70
|
+
this.$emit('update:modelValue', time)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<style>
|
|
79
|
+
|
|
80
|
+
.timepicker{
|
|
81
|
+
@apply inline-flex flex-row items-center bg-base-50;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.timepicker label{
|
|
85
|
+
@apply inline-block p-2;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.timepicker select{
|
|
89
|
+
@apply w-full p-2 appearance-none bg-transparent outline-none;
|
|
90
|
+
@apply border-[1px] border-text-200 hover:border-text-300 rounded-lg;
|
|
91
|
+
@apply text-center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.timepicker.timepicker-sm select{
|
|
95
|
+
@apply text-sm p-1;
|
|
96
|
+
}
|
|
97
|
+
.timepicker.timepicker-sm label{
|
|
98
|
+
@apply text-sm p-1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.timepicker.timepicker-lg select{
|
|
102
|
+
@apply text-lg p-3;
|
|
103
|
+
}
|
|
104
|
+
.timepicker.timepicker-lg label{
|
|
105
|
+
@apply text-lg p-3;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
</style>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<Teleport to=".dG9h">
|
|
4
|
+
<Transition name="dG9i">
|
|
5
|
+
<div v-if="state" :class="$style.toast" @mouseover="onMouseOver">
|
|
6
|
+
<slot></slot>
|
|
7
|
+
</div>
|
|
8
|
+
</Transition>
|
|
9
|
+
</Teleport>
|
|
10
|
+
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
|
|
15
|
+
export default{
|
|
16
|
+
|
|
17
|
+
props: {
|
|
18
|
+
|
|
19
|
+
state: [ Boolean, Number, String ],
|
|
20
|
+
|
|
21
|
+
dismissAfter: {
|
|
22
|
+
type: [ Number, String ],
|
|
23
|
+
default: 3000
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
emits: [ 'dismiss' ],
|
|
29
|
+
|
|
30
|
+
data(){
|
|
31
|
+
return {
|
|
32
|
+
intervalId: null
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
methods: {
|
|
37
|
+
|
|
38
|
+
onMouseOver(){
|
|
39
|
+
if(this.intervalId){
|
|
40
|
+
window.clearTimeout(this.intervalId)
|
|
41
|
+
this.intervalId = null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
watch:{
|
|
48
|
+
|
|
49
|
+
state(to){
|
|
50
|
+
if(to){
|
|
51
|
+
this.intervalId = window.setTimeout(() => {
|
|
52
|
+
if(this.state){
|
|
53
|
+
this.$emit('dismiss')
|
|
54
|
+
}
|
|
55
|
+
}, parseInt(this.dismissAfter))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style>
|
|
66
|
+
|
|
67
|
+
.dG9h{
|
|
68
|
+
@apply fixed top-0 left-0 right-0;
|
|
69
|
+
z-index: 61;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dG9i-enter-active,
|
|
73
|
+
.dG9i-leave-active {
|
|
74
|
+
transition: all 300ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dG9i-enter-from,
|
|
78
|
+
.dG9i-leave-to {
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transform: translate3d(0, -10vh, 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
</style>
|
|
84
|
+
|
|
85
|
+
<style module>
|
|
86
|
+
|
|
87
|
+
.toast{
|
|
88
|
+
@apply max-w-[90vw] md:max-w-[640px] bg-base-400 mt-4 mx-auto rounded-xl border-[1px] border-text-50;
|
|
89
|
+
@apply min-h-[var(--h-cp)];
|
|
90
|
+
z-index: 61;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.comp">
|
|
3
|
+
<div ref="scroller" :class="$style.scroller" :style="scrollerStyle">
|
|
4
|
+
<div v-if="visibleItems && visibleItems.length > 0" :class="$style.spacer" ref="spacer" :style="spacerStyle">
|
|
5
|
+
<div v-for="(item, index) in visibleItems" :key="item" :data-id="item.id"
|
|
6
|
+
@click="select(item, index)"
|
|
7
|
+
:class="[ $style.item, selectedIndex === ((item && item.id) ? item.id : visibleStartIndex + index) ? $style.trSelected : '', item._highlight ? $style.highlight : '' ].join(' ')">
|
|
8
|
+
<slot name="item" :item="item" :index="index"></slot>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div v-else class="flex p-8 justify-center items-center">
|
|
12
|
+
<slot v-if="$slots['empty']" name="empty"></slot>
|
|
13
|
+
<div v-else>No data</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div :class="$style.calc" v-if="items && items.length > 0" ref="calc">
|
|
17
|
+
<slot name="item" :item="items[0]" :index="0"></slot>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
|
|
24
|
+
import throttle from "lodash/throttle";
|
|
25
|
+
|
|
26
|
+
export default{
|
|
27
|
+
|
|
28
|
+
emits: [ 'scroll-end' ],
|
|
29
|
+
|
|
30
|
+
props:{
|
|
31
|
+
|
|
32
|
+
items:{
|
|
33
|
+
type: Array, Object
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
computed:{
|
|
39
|
+
|
|
40
|
+
visibleStartIndex(){
|
|
41
|
+
return Math.round(this.scrollTop / this.itemHeight)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
visibleItems(){
|
|
45
|
+
if(this.itemHeight <= 0) return []
|
|
46
|
+
return this.items ? this.items.slice(this.visibleStartIndex, this.visibleStartIndex + this.maxVisibleItems) : []
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
spacerStyle(){
|
|
50
|
+
return {
|
|
51
|
+
transform: "translateY(" + (this.visibleStartIndex * this.itemHeight) + "px)"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
scrollerStyle(){
|
|
56
|
+
if(!this.items || this.items.length < 1)
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
const height = (this.items.length * this.itemHeight)
|
|
60
|
+
//console.log('scrollerStyle', height)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
height: height + 'px'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
data(){
|
|
70
|
+
return {
|
|
71
|
+
scrollTop: 0,
|
|
72
|
+
itemHeight: 0,
|
|
73
|
+
maxVisibleItems: 0,
|
|
74
|
+
isOnEndScroll: false,
|
|
75
|
+
selectedIndex: -1
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
mounted() {
|
|
80
|
+
this.init()
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
methods:{
|
|
84
|
+
|
|
85
|
+
init(){
|
|
86
|
+
this.$el.addEventListener(
|
|
87
|
+
"scroll",
|
|
88
|
+
this.handleScroll,
|
|
89
|
+
this.passiveScrollSupported() ? { passive: true } : false
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
this.resize()
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
select(item, index){
|
|
96
|
+
this.selectedIndex = (item && item.id) ? item.id : this.visibleStartIndex + index
|
|
97
|
+
if(item._highlight){
|
|
98
|
+
delete item._highlight
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
handleScroll: throttle(function(){
|
|
103
|
+
this.scrollTop = this.$el.scrollTop
|
|
104
|
+
|
|
105
|
+
if(this.scrollTop > this.$refs.scroller.offsetHeight - this.$el.clientHeight - this.itemHeight){
|
|
106
|
+
if(!this.isOnEndScroll){
|
|
107
|
+
this.$emit('scroll-end')
|
|
108
|
+
this.isOnEndScroll = true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else{
|
|
112
|
+
if(this.isOnEndScroll){
|
|
113
|
+
this.isOnEndScroll = false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}, 10),
|
|
117
|
+
|
|
118
|
+
passiveScrollSupported() {
|
|
119
|
+
let passiveSupported = false;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const options = {
|
|
123
|
+
get passive() {
|
|
124
|
+
passiveSupported = true;
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
window.addEventListener("test", null, options);
|
|
129
|
+
window.removeEventListener("test", null, options);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
passiveSupported = false;
|
|
132
|
+
}
|
|
133
|
+
return passiveSupported;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async resize(){
|
|
137
|
+
|
|
138
|
+
this.$nextTick(() => {
|
|
139
|
+
if(this.$refs.calc){
|
|
140
|
+
const elHeight = parseInt(window.getComputedStyle(this.$el).height !== '0px' ?
|
|
141
|
+
window.getComputedStyle(this.$el).height :
|
|
142
|
+
window.getComputedStyle(this.$el).maxHeight)
|
|
143
|
+
this.itemHeight = parseInt(window.getComputedStyle(this.$refs.calc).height)
|
|
144
|
+
this.maxVisibleItems = elHeight > 0 ? Math.ceil(elHeight / this.itemHeight) + 1 : this.items.length
|
|
145
|
+
//console.log('Virtual scroll resize', { elHeight, itemHeight:this.itemHeight, maxVisibleItems:this.maxVisibleItems })
|
|
146
|
+
|
|
147
|
+
if(this.itemHeight <= 0){
|
|
148
|
+
console.error('[VirtualScroll] Unable to calculate item height, make sure not async component.')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
watch: {
|
|
158
|
+
|
|
159
|
+
items: {
|
|
160
|
+
deep: true,
|
|
161
|
+
handler(){
|
|
162
|
+
this.resize()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<style module>
|
|
173
|
+
|
|
174
|
+
.comp{
|
|
175
|
+
@apply relative overflow-auto max-h-[100vh];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.scroller{
|
|
179
|
+
position: relative;
|
|
180
|
+
overflow: hidden;
|
|
181
|
+
will-change: auto;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.spacer{
|
|
185
|
+
will-change: auto;
|
|
186
|
+
position: relative;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.item:hover{
|
|
190
|
+
@apply bg-primary-50;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.item.trSelected{
|
|
194
|
+
@apply bg-primary-200;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.highlight{
|
|
198
|
+
animation: highlight 1s 1 forwards;
|
|
199
|
+
}
|
|
200
|
+
@keyframes highlight {
|
|
201
|
+
0% {
|
|
202
|
+
@apply bg-primary-50;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
100% {
|
|
206
|
+
@apply bg-primary-100;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.calc{
|
|
211
|
+
@apply absolute invisible;
|
|
212
|
+
top: -10000px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
</style>
|