@mixd-id/web-scaffold 0.1.230406357 → 0.1.230406358
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/package.json +1 -1
- package/src/components/VirtualGrid.vue +201 -210
package/package.json
CHANGED
|
@@ -1,44 +1,37 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<div ref="
|
|
5
|
-
<div
|
|
6
|
-
<div
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<slot name="item" :item="item" :index="index">
|
|
10
|
-
{{ item }}
|
|
11
|
-
</slot>
|
|
12
|
-
</div>
|
|
13
|
-
<slot name="end"></slot>
|
|
2
|
+
<div :class="$style.virtualGrid" @click="resize">
|
|
3
|
+
|
|
4
|
+
<div ref="scroller" :class="$style.scroller" :style="scrollerStyle">
|
|
5
|
+
<div :class="spacerClass" ref="spacer" :style="spacerStyle">
|
|
6
|
+
<div v-for="(item, index) in visibleItems" :key="item" :data-id="item.id"
|
|
7
|
+
@click="">
|
|
8
|
+
<slot name="item" :item="item" :index="index"></slot>
|
|
14
9
|
</div>
|
|
15
10
|
</div>
|
|
16
11
|
</div>
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</div>
|
|
13
|
+
<div :class="$style.calc" v-if="items && items.length > 0" ref="calc">
|
|
14
|
+
<slot name="item" :item="items[0]" :index="0"></slot>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<Teleport v-if="optBar" :to="optBar">
|
|
18
|
+
<div class="flex flex-row gap-2 items-center">
|
|
19
|
+
<small class="text-text-400">Column</small>
|
|
20
|
+
<select v-model="config.gridColumn"
|
|
21
|
+
class="appearance-none outline-none text-text-400 w-[20px] bg-transparent border-[1px] border-text-100 text-center">
|
|
22
|
+
<option v-for="i in 8" :value="i">{{ i }}</option>
|
|
23
|
+
</select>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="flex flex-row gap-2 items-center">
|
|
26
|
+
<small class="text-text-400">Gap</small>
|
|
27
|
+
<select v-model="config.gridGap"
|
|
28
|
+
class="appearance-none outline-none text-text-400 w-[20px] bg-transparent border-[1px] border-text-100 text-center">
|
|
29
|
+
<option v-for="i in 8" :value="`gap-${i}`">{{ i }}</option>
|
|
30
|
+
</select>
|
|
31
|
+
</div>
|
|
32
|
+
</Teleport>
|
|
33
|
+
|
|
34
|
+
</div>
|
|
42
35
|
</template>
|
|
43
36
|
|
|
44
37
|
<script>
|
|
@@ -47,202 +40,200 @@ import throttle from "lodash/throttle";
|
|
|
47
40
|
|
|
48
41
|
export default{
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
emits: [ 'scroll-end' ],
|
|
51
44
|
|
|
52
|
-
|
|
45
|
+
props: {
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
column: {
|
|
48
|
+
type: [ Number, String ],
|
|
49
|
+
default: 1
|
|
50
|
+
},
|
|
58
51
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
gap: {
|
|
53
|
+
type: [ String ],
|
|
54
|
+
default: 'gap-0'
|
|
55
|
+
},
|
|
63
56
|
|
|
64
57
|
containerClass: String,
|
|
65
58
|
|
|
66
|
-
|
|
59
|
+
items: Array,
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
pinned: Function,
|
|
69
62
|
|
|
70
|
-
|
|
63
|
+
optBar: String,
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
config: Object
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
},
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
methods: {
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
handleScroll: throttle(function(){
|
|
72
|
+
this.scrollTop = this.$el.scrollTop
|
|
80
73
|
|
|
81
|
-
|
|
74
|
+
if(this.scrollTop > this.$refs.scroller.offsetHeight - this.$el.clientHeight - this.itemHeight){
|
|
75
|
+
if(!this.isOnEndScroll){
|
|
76
|
+
this.$emit('scroll-end')
|
|
77
|
+
this.isOnEndScroll = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else{
|
|
81
|
+
if(this.isOnEndScroll){
|
|
82
|
+
this.isOnEndScroll = false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, 16),
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
else{
|
|
90
|
-
if(this.isOnEndScroll){
|
|
91
|
-
this.isOnEndScroll = false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}, 16),
|
|
87
|
+
init(){
|
|
88
|
+
this.$el.addEventListener(
|
|
89
|
+
"scroll",
|
|
90
|
+
this.handleScroll,
|
|
91
|
+
this.passiveScrollSupported() ? { passive: true } : false
|
|
92
|
+
)
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"scroll",
|
|
99
|
-
this.handleScroll,
|
|
100
|
-
this.passiveScrollSupported() ? { passive: true } : false
|
|
101
|
-
)
|
|
94
|
+
this.resize()
|
|
95
|
+
},
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
passiveScrollSupported() {
|
|
98
|
+
let passiveSupported = false;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const options = {
|
|
102
|
+
get passive() {
|
|
103
|
+
passiveSupported = true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
window.addEventListener("test", null, options);
|
|
108
|
+
window.removeEventListener("test", null, options);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
passiveSupported = false;
|
|
111
|
+
}
|
|
112
|
+
return passiveSupported;
|
|
113
|
+
},
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
resetState(){
|
|
116
|
+
this.state = 1
|
|
117
|
+
},
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
const options = {
|
|
111
|
-
get passive() {
|
|
112
|
-
passiveSupported = true;
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
window.addEventListener("test", null, options);
|
|
117
|
-
window.removeEventListener("test", null, options);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
passiveSupported = false;
|
|
120
|
-
}
|
|
121
|
-
return passiveSupported;
|
|
122
|
-
},
|
|
119
|
+
async resize(){
|
|
123
120
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
this.$nextTick(() => {
|
|
122
|
+
if(this.$refs.calc){
|
|
123
|
+
const elHeight = parseInt(window.getComputedStyle(this.$el).height !== '0px' ?
|
|
124
|
+
window.getComputedStyle(this.$el).height :
|
|
125
|
+
window.getComputedStyle(this.$el).maxHeight)
|
|
127
126
|
|
|
128
|
-
|
|
127
|
+
if(isNaN(elHeight)) return
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
if(this.$refs.calc){
|
|
132
|
-
const elHeight = parseInt(window.getComputedStyle(this.$refs.container).height !== '0px' ?
|
|
133
|
-
window.getComputedStyle(this.$refs.container).height :
|
|
134
|
-
window.getComputedStyle(this.$refs.container).maxHeight)
|
|
129
|
+
this.itemHeight = parseFloat(window.getComputedStyle(this.$refs.calc).height.replace('px', ''))
|
|
135
130
|
|
|
136
|
-
|
|
131
|
+
this.itemGap = parseFloat(window.getComputedStyle(this.$refs.spacer)['row-gap'].replace('px', ''));
|
|
137
132
|
|
|
138
|
-
|
|
133
|
+
this.maxVisibleItems = elHeight > 0 ? (Math.ceil(elHeight / this.itemHeight) * this.column) : this.items.length
|
|
139
134
|
|
|
140
|
-
|
|
135
|
+
if(this.itemHeight <= 0){
|
|
136
|
+
console.error('Unable to calculate virtual grid item height, async component not supported.')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
},
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
})
|
|
143
|
+
setState(state){
|
|
144
|
+
this.state = state
|
|
145
|
+
}
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
},
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
this.state = state
|
|
154
|
-
}
|
|
149
|
+
computed: {
|
|
155
150
|
|
|
156
|
-
|
|
151
|
+
scrollerStyle(){
|
|
152
|
+
if(!this.items || this.items.length < 1)
|
|
153
|
+
return {}
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
const rowCount = Math.ceil((this.items.length / this.column))
|
|
156
|
+
const height = (rowCount * this.itemHeight) + (this.itemGap * (rowCount - 1))
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
return {
|
|
159
|
+
height: height + 'px'
|
|
160
|
+
}
|
|
161
|
+
},
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
]
|
|
189
|
-
}
|
|
190
|
-
return this.items
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
spacerClass(){
|
|
194
|
-
return [
|
|
195
|
-
this.$style.spacer,
|
|
163
|
+
sortedItems(){
|
|
164
|
+
if(!Array.isArray(this.items)) return []
|
|
165
|
+
|
|
166
|
+
if(typeof this.pinned === 'function'){
|
|
167
|
+
const pinnedItems = []
|
|
168
|
+
const unpinnedItems = []
|
|
169
|
+
this.items.forEach((item) => {
|
|
170
|
+
if(this.pinned(item))
|
|
171
|
+
pinnedItems.push(item)
|
|
172
|
+
else
|
|
173
|
+
unpinnedItems.push(item)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
return [
|
|
177
|
+
...pinnedItems,
|
|
178
|
+
...unpinnedItems
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
return this.items
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
spacerClass(){
|
|
185
|
+
return [
|
|
186
|
+
this.$style.spacer,
|
|
196
187
|
this.containerClass,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
188
|
+
this.gap
|
|
189
|
+
]
|
|
190
|
+
.join(' ')
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
spacerStyle(){
|
|
194
|
+
return {
|
|
195
|
+
transform: "translateY(" + (this.visibleStartIndex * (this.itemHeight + this.itemGap)) + "px)",
|
|
196
|
+
'grid-template-columns': `repeat(${this.column}, minmax(0, 1fr))`,
|
|
197
|
+
gap: this.gap
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
visibleItems(){
|
|
202
|
+
if(this.itemHeight <= 0) return []
|
|
203
|
+
return this.sortedItems.slice(this.visibleStartIndex * this.column, (this.visibleStartIndex * this.column) + this.maxVisibleItems)
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
visibleStartIndex(){
|
|
207
|
+
return Math.floor((this.scrollTop < 0 ? 0 : this.scrollTop) / (this.itemHeight + this.itemGap))
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
data(){
|
|
213
|
+
return {
|
|
214
|
+
scrollTop: 0,
|
|
215
|
+
itemHeight: 0,
|
|
216
|
+
itemGap: 0,
|
|
217
|
+
maxVisibleItems: 0,
|
|
218
|
+
isOnEndScroll: false,
|
|
219
|
+
selectedIndex: -1,
|
|
220
|
+
state: 1,
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
mounted() {
|
|
225
|
+
this.init()
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
watch: {
|
|
229
|
+
|
|
230
|
+
column(to){
|
|
231
|
+
this.resize()
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
gap(to){
|
|
235
|
+
this.resize()
|
|
236
|
+
},
|
|
246
237
|
|
|
247
238
|
items: {
|
|
248
239
|
deep: true,
|
|
@@ -255,7 +246,7 @@ export default{
|
|
|
255
246
|
}
|
|
256
247
|
},
|
|
257
248
|
|
|
258
|
-
|
|
249
|
+
}
|
|
259
250
|
|
|
260
251
|
}
|
|
261
252
|
|
|
@@ -264,25 +255,25 @@ export default{
|
|
|
264
255
|
<style module>
|
|
265
256
|
|
|
266
257
|
.virtualGrid{
|
|
267
|
-
|
|
258
|
+
@apply flex-1 overflow-y-auto;
|
|
268
259
|
}
|
|
269
260
|
|
|
270
261
|
.scroller{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
262
|
+
position: relative;
|
|
263
|
+
overflow: hidden;
|
|
264
|
+
will-change: auto;
|
|
265
|
+
@apply min-w-full;
|
|
275
266
|
}
|
|
276
267
|
|
|
277
268
|
.spacer{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
269
|
+
will-change: auto;
|
|
270
|
+
position: relative;
|
|
271
|
+
@apply grid;
|
|
281
272
|
}
|
|
282
273
|
|
|
283
274
|
.calc{
|
|
284
|
-
|
|
285
|
-
|
|
275
|
+
@apply absolute invisible max-w-full;
|
|
276
|
+
top: -10000px;
|
|
286
277
|
|
|
287
278
|
}
|
|
288
279
|
|