@v-c/virtual-list 1.0.0 → 1.0.2
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/dist/List.cjs +18 -9
- package/dist/List.js +19 -10
- package/dist/hooks/useHeights.cjs +1 -1
- package/dist/hooks/useHeights.js +2 -2
- package/package.json +6 -2
- package/bump.config.ts +0 -6
- package/docs/animate.less +0 -31
- package/docs/animate.vue +0 -159
- package/docs/basic.vue +0 -176
- package/docs/height.vue +0 -48
- package/docs/nest.vue +0 -60
- package/docs/no-virtual.vue +0 -127
- package/docs/switch.vue +0 -102
- package/docs/virtual-list.stories.vue +0 -35
- package/src/Filler.tsx +0 -73
- package/src/Item.tsx +0 -35
- package/src/List.tsx +0 -642
- package/src/ScrollBar.tsx +0 -287
- package/src/__tests__/List.test.ts +0 -59
- package/src/hooks/useDiffItem.ts +0 -28
- package/src/hooks/useFrameWheel.ts +0 -142
- package/src/hooks/useGetSize.ts +0 -45
- package/src/hooks/useHeights.ts +0 -107
- package/src/hooks/useMobileTouchMove.ts +0 -132
- package/src/hooks/useOriginScroll.ts +0 -48
- package/src/hooks/useScrollDrag.ts +0 -124
- package/src/hooks/useScrollTo.tsx +0 -184
- package/src/index.ts +0 -5
- package/src/interface.ts +0 -32
- package/src/utils/CacheMap.ts +0 -42
- package/src/utils/isFirefox.ts +0 -3
- package/src/utils/scrollbarUtil.ts +0 -10
- package/tsconfig.json +0 -7
- package/vite.config.ts +0 -18
- package/vitest.config.ts +0 -11
package/src/List.tsx
DELETED
|
@@ -1,642 +0,0 @@
|
|
|
1
|
-
import type { Key } from '@v-c/util/dist/type'
|
|
2
|
-
|
|
3
|
-
import type { CSSProperties, PropType, VNode } from 'vue'
|
|
4
|
-
import type { InnerProps } from './Filler'
|
|
5
|
-
import type { ExtraRenderInfo } from './interface'
|
|
6
|
-
import type { ScrollBarDirectionType, ScrollBarRef } from './ScrollBar'
|
|
7
|
-
import ResizeObserver from '@v-c/resize-observer'
|
|
8
|
-
import { pureAttrs } from '@v-c/util/dist/props-util'
|
|
9
|
-
import {
|
|
10
|
-
computed,
|
|
11
|
-
defineComponent,
|
|
12
|
-
ref,
|
|
13
|
-
shallowRef,
|
|
14
|
-
watch,
|
|
15
|
-
} from 'vue'
|
|
16
|
-
import Filler from './Filler'
|
|
17
|
-
import useDiffItem from './hooks/useDiffItem'
|
|
18
|
-
import useFrameWheel from './hooks/useFrameWheel'
|
|
19
|
-
import { useGetSize } from './hooks/useGetSize'
|
|
20
|
-
import useHeights from './hooks/useHeights'
|
|
21
|
-
import useMobileTouchMove from './hooks/useMobileTouchMove'
|
|
22
|
-
import useScrollDrag from './hooks/useScrollDrag'
|
|
23
|
-
import useScrollTo from './hooks/useScrollTo'
|
|
24
|
-
import Item from './Item'
|
|
25
|
-
import ScrollBar from './ScrollBar'
|
|
26
|
-
import { getSpinSize } from './utils/scrollbarUtil'
|
|
27
|
-
|
|
28
|
-
const EMPTY_DATA: any[] = []
|
|
29
|
-
|
|
30
|
-
const ScrollStyle: CSSProperties = {
|
|
31
|
-
overflowY: 'auto',
|
|
32
|
-
overflowAnchor: 'none',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ScrollInfo {
|
|
36
|
-
x: number
|
|
37
|
-
y: number
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type ScrollTo = (arg?: number | ScrollConfig | null) => void
|
|
41
|
-
|
|
42
|
-
export interface ListRef {
|
|
43
|
-
nativeElement?: HTMLDivElement
|
|
44
|
-
scrollTo: ScrollTo
|
|
45
|
-
getScrollInfo: () => ScrollInfo
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface ScrollPos {
|
|
49
|
-
left?: number
|
|
50
|
-
top?: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ScrollTarget {
|
|
54
|
-
index?: number
|
|
55
|
-
key?: Key
|
|
56
|
-
align?: 'top' | 'bottom' | 'auto'
|
|
57
|
-
offset?: number
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type ScrollConfig = ScrollTarget | ScrollPos
|
|
61
|
-
|
|
62
|
-
export interface ListProps {
|
|
63
|
-
prefixCls?: string
|
|
64
|
-
data?: any[]
|
|
65
|
-
height?: number
|
|
66
|
-
itemHeight?: number
|
|
67
|
-
fullHeight?: boolean
|
|
68
|
-
itemKey: Key | ((item: any) => Key)
|
|
69
|
-
component?: string
|
|
70
|
-
virtual?: boolean
|
|
71
|
-
direction?: ScrollBarDirectionType
|
|
72
|
-
/**
|
|
73
|
-
* By default `scrollWidth` is same as container.
|
|
74
|
-
* When set this, it will show the horizontal scrollbar and
|
|
75
|
-
* `scrollWidth` will be used as the real width instead of container width.
|
|
76
|
-
* When set, `virtual` will always be enabled.
|
|
77
|
-
*/
|
|
78
|
-
scrollWidth?: number
|
|
79
|
-
styles?: {
|
|
80
|
-
horizontalScrollBar?: CSSProperties
|
|
81
|
-
horizontalScrollBarThumb?: CSSProperties
|
|
82
|
-
verticalScrollBar?: CSSProperties
|
|
83
|
-
verticalScrollBarThumb?: CSSProperties
|
|
84
|
-
}
|
|
85
|
-
showScrollBar?: boolean | 'optional'
|
|
86
|
-
onScroll?: (e: Event) => void
|
|
87
|
-
onVirtualScroll?: (info: ScrollInfo) => void
|
|
88
|
-
onVisibleChange?: (visibleList: any[], fullList: any[]) => void
|
|
89
|
-
innerProps?: InnerProps
|
|
90
|
-
extraRender?: (info: ExtraRenderInfo) => VNode
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export default defineComponent({
|
|
94
|
-
name: 'VirtualList',
|
|
95
|
-
props: {
|
|
96
|
-
prefixCls: { type: String, default: 'vc-virtual-list' },
|
|
97
|
-
data: { type: Array as PropType<any[]> },
|
|
98
|
-
height: Number,
|
|
99
|
-
itemHeight: Number,
|
|
100
|
-
fullHeight: { type: Boolean, default: true },
|
|
101
|
-
itemKey: { type: [String, Number, Function] as PropType<Key | ((item: any) => Key)>, required: true },
|
|
102
|
-
component: { type: String, default: 'div' },
|
|
103
|
-
direction: { type: String as PropType<ScrollBarDirectionType> },
|
|
104
|
-
scrollWidth: Number,
|
|
105
|
-
styles: Object,
|
|
106
|
-
showScrollBar: { type: [Boolean, String] as PropType<boolean | 'optional'>, default: 'optional' },
|
|
107
|
-
virtual: { type: Boolean, default: true },
|
|
108
|
-
onScroll: Function as PropType<(e: Event) => void>,
|
|
109
|
-
onVirtualScroll: Function as PropType<(info: ScrollInfo) => void>,
|
|
110
|
-
onVisibleChange: Function as PropType<(visibleList: any[], fullList: any[]) => void>,
|
|
111
|
-
innerProps: Object as PropType<InnerProps>,
|
|
112
|
-
extraRender: Function as PropType<(info: ExtraRenderInfo) => VNode>,
|
|
113
|
-
},
|
|
114
|
-
inheritAttrs: false,
|
|
115
|
-
setup(props, { expose, attrs, slots }) {
|
|
116
|
-
const itemHeight = computed(() => props.itemHeight)
|
|
117
|
-
// =============================== Item Key ===============================
|
|
118
|
-
const getKey = (item: any): Key => {
|
|
119
|
-
if (typeof props.itemKey === 'function') {
|
|
120
|
-
return props.itemKey(item)
|
|
121
|
-
}
|
|
122
|
-
return item?.[props.itemKey as string]
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ================================ Height ================================
|
|
126
|
-
const [setInstanceRef, collectHeight, heights, heightUpdatedMark] = useHeights(
|
|
127
|
-
getKey,
|
|
128
|
-
undefined,
|
|
129
|
-
undefined,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
// ================================= MISC =================================
|
|
133
|
-
const mergedData = computed(() => props.data || EMPTY_DATA)
|
|
134
|
-
|
|
135
|
-
const useVirtual = computed(
|
|
136
|
-
() => !!(props.virtual !== false && props.height && props.itemHeight),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
const containerHeight = computed(() =>
|
|
140
|
-
Object.values(heights.maps).reduce((total: number, curr: number) => total + curr, 0),
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
const inVirtual = computed(() => {
|
|
144
|
-
const data = mergedData.value
|
|
145
|
-
return (
|
|
146
|
-
useVirtual.value
|
|
147
|
-
&& data
|
|
148
|
-
&& (Math.max(props.itemHeight! * data.length, containerHeight.value) > props.height!
|
|
149
|
-
|| !!props.scrollWidth)
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
const componentRef = ref<HTMLDivElement>()
|
|
154
|
-
const fillerInnerRef = ref<HTMLDivElement>()
|
|
155
|
-
const containerRef = ref<HTMLDivElement>()
|
|
156
|
-
const verticalScrollBarRef = shallowRef<ScrollBarRef>()
|
|
157
|
-
const horizontalScrollBarRef = shallowRef<ScrollBarRef>()
|
|
158
|
-
|
|
159
|
-
const offsetTop = ref(0)
|
|
160
|
-
const offsetLeft = ref(0)
|
|
161
|
-
const scrollMoving = ref(false)
|
|
162
|
-
|
|
163
|
-
// ScrollBar related
|
|
164
|
-
const verticalScrollBarSpinSize = ref(0)
|
|
165
|
-
const horizontalScrollBarSpinSize = ref(0)
|
|
166
|
-
const contentScrollWidth = ref<number>(props.scrollWidth || 0)
|
|
167
|
-
|
|
168
|
-
// ========================== Visible Calculation =========================
|
|
169
|
-
const scrollHeight = ref(0)
|
|
170
|
-
const start = ref(0)
|
|
171
|
-
const end = ref(0)
|
|
172
|
-
const fillerOffset = ref<number | undefined>(undefined)
|
|
173
|
-
|
|
174
|
-
// ================================ Scroll ================================
|
|
175
|
-
function syncScrollTop(newTop: number | ((prev: number) => number)) {
|
|
176
|
-
let value: number
|
|
177
|
-
if (typeof newTop === 'function') {
|
|
178
|
-
value = newTop(offsetTop.value)
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
value = newTop
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const maxScrollHeight = scrollHeight!.value! - props.height!
|
|
185
|
-
const alignedTop = Math.max(0, Math.min(value, maxScrollHeight || 0))
|
|
186
|
-
|
|
187
|
-
if (componentRef.value) {
|
|
188
|
-
componentRef.value.scrollTop = alignedTop
|
|
189
|
-
}
|
|
190
|
-
offsetTop.value = alignedTop
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ================================ Range ================================
|
|
194
|
-
useDiffItem(mergedData, getKey)
|
|
195
|
-
|
|
196
|
-
watch(
|
|
197
|
-
[
|
|
198
|
-
inVirtual,
|
|
199
|
-
useVirtual,
|
|
200
|
-
offsetTop,
|
|
201
|
-
mergedData,
|
|
202
|
-
heightUpdatedMark,
|
|
203
|
-
() => props.height,
|
|
204
|
-
],
|
|
205
|
-
() => {
|
|
206
|
-
if (!useVirtual.value) {
|
|
207
|
-
scrollHeight.value = 0
|
|
208
|
-
start.value = 0
|
|
209
|
-
end.value = mergedData.value.length - 1
|
|
210
|
-
fillerOffset.value = undefined
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (!inVirtual.value) {
|
|
215
|
-
scrollHeight.value = fillerInnerRef.value?.offsetHeight || 0
|
|
216
|
-
start.value = 0
|
|
217
|
-
end.value = mergedData.value.length - 1
|
|
218
|
-
fillerOffset.value = undefined
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
let itemTop = 0
|
|
223
|
-
let startIndex: number | undefined
|
|
224
|
-
let startOffset: number | undefined
|
|
225
|
-
let endIndex: number | undefined
|
|
226
|
-
|
|
227
|
-
const dataLen = mergedData.value.length
|
|
228
|
-
const data = mergedData.value
|
|
229
|
-
|
|
230
|
-
for (let i = 0; i < dataLen; i += 1) {
|
|
231
|
-
const item = data[i]
|
|
232
|
-
const key = getKey(item)
|
|
233
|
-
|
|
234
|
-
const cacheHeight = heights.get(key)
|
|
235
|
-
const currentItemBottom = itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight)
|
|
236
|
-
|
|
237
|
-
if (currentItemBottom >= offsetTop.value && startIndex === undefined) {
|
|
238
|
-
startIndex = i
|
|
239
|
-
startOffset = itemTop
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (currentItemBottom > offsetTop.value + props.height! && endIndex === undefined) {
|
|
243
|
-
endIndex = i
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
itemTop = currentItemBottom
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (startIndex === undefined) {
|
|
250
|
-
startIndex = 0
|
|
251
|
-
startOffset = 0
|
|
252
|
-
endIndex = Math.ceil(props.height! / props.itemHeight!)
|
|
253
|
-
}
|
|
254
|
-
if (endIndex === undefined) {
|
|
255
|
-
endIndex = mergedData.value.length - 1
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
endIndex = Math.min(endIndex + 1, mergedData.value.length - 1)
|
|
259
|
-
|
|
260
|
-
scrollHeight.value = itemTop
|
|
261
|
-
start.value = startIndex
|
|
262
|
-
end.value = endIndex
|
|
263
|
-
fillerOffset.value = startOffset
|
|
264
|
-
},
|
|
265
|
-
{ immediate: true },
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
// Sync scroll top when height changes
|
|
269
|
-
watch(
|
|
270
|
-
scrollHeight,
|
|
271
|
-
() => {
|
|
272
|
-
const changedRecord = heights.getRecord()
|
|
273
|
-
if (changedRecord.size === 1) {
|
|
274
|
-
const recordKey = Array.from(changedRecord.keys())[0]
|
|
275
|
-
const prevCacheHeight = changedRecord.get(recordKey)
|
|
276
|
-
|
|
277
|
-
const startItem = mergedData.value[start.value]
|
|
278
|
-
if (startItem && prevCacheHeight === undefined) {
|
|
279
|
-
const startIndexKey = getKey(startItem)
|
|
280
|
-
if (startIndexKey === recordKey) {
|
|
281
|
-
const realStartHeight = heights.get(recordKey)
|
|
282
|
-
const diffHeight = realStartHeight - props.itemHeight!
|
|
283
|
-
syncScrollTop(ori => ori + diffHeight)
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
heights.resetRecord()
|
|
289
|
-
},
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
// ================================= Size =================================
|
|
293
|
-
const size = ref({ width: 0, height: props.height || 0 })
|
|
294
|
-
|
|
295
|
-
const onHolderResize = (sizeInfo: { offsetWidth: number, offsetHeight: number }) => {
|
|
296
|
-
size.value = {
|
|
297
|
-
width: sizeInfo.offsetWidth,
|
|
298
|
-
height: sizeInfo.offsetHeight,
|
|
299
|
-
}
|
|
300
|
-
contentScrollWidth.value = props.scrollWidth ?? sizeInfo.offsetWidth
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// =============================== Scroll ===============================
|
|
304
|
-
const isRTL = computed(() => props.direction === 'rtl')
|
|
305
|
-
|
|
306
|
-
const getVirtualScrollInfo = () => ({
|
|
307
|
-
x: isRTL.value ? -offsetLeft.value : offsetLeft.value,
|
|
308
|
-
y: offsetTop.value,
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
const lastVirtualScrollInfo = ref(getVirtualScrollInfo())
|
|
312
|
-
|
|
313
|
-
const triggerScroll = (params?: { x?: number, y?: number }) => {
|
|
314
|
-
if (props.onVirtualScroll) {
|
|
315
|
-
const nextInfo = { ...getVirtualScrollInfo(), ...params }
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
lastVirtualScrollInfo.value.x !== nextInfo.x
|
|
319
|
-
|| lastVirtualScrollInfo.value.y !== nextInfo.y
|
|
320
|
-
) {
|
|
321
|
-
props.onVirtualScroll(nextInfo)
|
|
322
|
-
lastVirtualScrollInfo.value = nextInfo
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ========================== Scroll Position ===========================
|
|
328
|
-
const horizontalRange = computed(() =>
|
|
329
|
-
Math.max(0, (contentScrollWidth.value || 0) - size.value.width),
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
const isScrollAtTop = computed(() => offsetTop.value === 0)
|
|
333
|
-
const isScrollAtBottom = computed(() => offsetTop.value + props.height! >= scrollHeight.value)
|
|
334
|
-
const isScrollAtLeft = computed(() => offsetLeft.value === 0)
|
|
335
|
-
const isScrollAtRight = computed(() => offsetLeft.value >= horizontalRange.value)
|
|
336
|
-
|
|
337
|
-
const keepInHorizontalRange = (nextOffsetLeft: number) => {
|
|
338
|
-
const max = horizontalRange.value
|
|
339
|
-
return Math.max(0, Math.min(nextOffsetLeft, max))
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ========================== Wheel & Touch =========================
|
|
343
|
-
const delayHideScrollBar = () => {
|
|
344
|
-
verticalScrollBarRef.value?.delayHidden()
|
|
345
|
-
horizontalScrollBarRef.value?.delayHidden()
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const [onWheel] = useFrameWheel(
|
|
349
|
-
inVirtual,
|
|
350
|
-
isScrollAtTop,
|
|
351
|
-
isScrollAtBottom,
|
|
352
|
-
isScrollAtLeft,
|
|
353
|
-
isScrollAtRight,
|
|
354
|
-
horizontalRange.value > 0,
|
|
355
|
-
(offsetY, isHorizontal) => {
|
|
356
|
-
if (isHorizontal) {
|
|
357
|
-
const next = isRTL.value ? offsetLeft.value - offsetY : offsetLeft.value + offsetY
|
|
358
|
-
const aligned = keepInHorizontalRange(next)
|
|
359
|
-
offsetLeft.value = aligned
|
|
360
|
-
triggerScroll({ x: isRTL.value ? -aligned : aligned })
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
syncScrollTop(top => top + offsetY)
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
useMobileTouchMove(
|
|
369
|
-
inVirtual,
|
|
370
|
-
componentRef,
|
|
371
|
-
(isHorizontal, offset, _smoothOffset, _e) => {
|
|
372
|
-
if (isHorizontal) {
|
|
373
|
-
const next = isRTL.value ? offsetLeft.value - offset : offsetLeft.value + offset
|
|
374
|
-
const aligned = keepInHorizontalRange(next)
|
|
375
|
-
offsetLeft.value = aligned
|
|
376
|
-
triggerScroll({ x: isRTL.value ? -aligned : aligned })
|
|
377
|
-
return true
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
syncScrollTop(top => top + offset)
|
|
381
|
-
return true
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
)
|
|
385
|
-
|
|
386
|
-
useScrollDrag(
|
|
387
|
-
inVirtual,
|
|
388
|
-
componentRef,
|
|
389
|
-
(offset) => {
|
|
390
|
-
syncScrollTop(top => top + offset)
|
|
391
|
-
},
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
// ========================== ScrollBar =========================
|
|
395
|
-
const onScrollBar = (newScrollOffset: number, horizontal?: boolean) => {
|
|
396
|
-
const newOffset = newScrollOffset
|
|
397
|
-
if (horizontal) {
|
|
398
|
-
offsetLeft.value = newOffset
|
|
399
|
-
triggerScroll({ x: isRTL.value ? -newOffset : newOffset })
|
|
400
|
-
}
|
|
401
|
-
else {
|
|
402
|
-
syncScrollTop(newOffset)
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const onScrollbarStartMove = () => {
|
|
407
|
-
scrollMoving.value = true
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const onScrollbarStopMove = () => {
|
|
411
|
-
scrollMoving.value = false
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Calculate ScrollBar spin size
|
|
415
|
-
watch(
|
|
416
|
-
[() => props.height, scrollHeight, inVirtual, () => size.value.height],
|
|
417
|
-
() => {
|
|
418
|
-
if (inVirtual.value && props.height && scrollHeight.value) {
|
|
419
|
-
verticalScrollBarSpinSize.value = getSpinSize(size.value.height, scrollHeight.value)
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
{ immediate: true },
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
watch(
|
|
426
|
-
[() => size.value.width, () => contentScrollWidth.value],
|
|
427
|
-
() => {
|
|
428
|
-
if (inVirtual.value && contentScrollWidth.value) {
|
|
429
|
-
horizontalScrollBarSpinSize.value = getSpinSize(size.value.width, contentScrollWidth.value)
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
{ immediate: true },
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
watch(
|
|
436
|
-
() => props.scrollWidth,
|
|
437
|
-
(val) => {
|
|
438
|
-
contentScrollWidth.value = val ?? size.value.width
|
|
439
|
-
offsetLeft.value = keepInHorizontalRange(offsetLeft.value)
|
|
440
|
-
},
|
|
441
|
-
{ immediate: true },
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
function onFallbackScroll(e: Event) {
|
|
445
|
-
const target = e.currentTarget as HTMLDivElement
|
|
446
|
-
const newScrollTop = target.scrollTop
|
|
447
|
-
if (newScrollTop !== offsetTop.value) {
|
|
448
|
-
syncScrollTop(newScrollTop)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
props.onScroll?.(e)
|
|
452
|
-
triggerScroll()
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// ================================= Ref ==================================
|
|
456
|
-
const scrollTo = useScrollTo(
|
|
457
|
-
componentRef as any,
|
|
458
|
-
mergedData,
|
|
459
|
-
heights,
|
|
460
|
-
itemHeight as any,
|
|
461
|
-
getKey,
|
|
462
|
-
() => collectHeight(true),
|
|
463
|
-
syncScrollTop,
|
|
464
|
-
delayHideScrollBar,
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
expose({
|
|
468
|
-
nativeElement: containerRef,
|
|
469
|
-
getScrollInfo: getVirtualScrollInfo,
|
|
470
|
-
scrollTo: (config: any) => {
|
|
471
|
-
function isPosScroll(arg: any): arg is ScrollPos {
|
|
472
|
-
return arg && typeof arg === 'object' && ('left' in arg || 'top' in arg)
|
|
473
|
-
}
|
|
474
|
-
if (isPosScroll(config)) {
|
|
475
|
-
if (config.left !== undefined) {
|
|
476
|
-
offsetLeft.value = keepInHorizontalRange(config.left)
|
|
477
|
-
}
|
|
478
|
-
scrollTo(config.top as any)
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
scrollTo(config)
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
// ================================ Effect ================================
|
|
487
|
-
watch(
|
|
488
|
-
[start, end, mergedData],
|
|
489
|
-
() => {
|
|
490
|
-
if (props.onVisibleChange) {
|
|
491
|
-
const renderList = mergedData.value.slice(start.value, end.value + 1)
|
|
492
|
-
props.onVisibleChange(renderList, mergedData.value)
|
|
493
|
-
}
|
|
494
|
-
},
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
const getSize = useGetSize(mergedData, getKey, heights, itemHeight as any)
|
|
498
|
-
|
|
499
|
-
return () => {
|
|
500
|
-
// ================================ Render ================================
|
|
501
|
-
const renderChildren = () => {
|
|
502
|
-
const children: VNode[] = []
|
|
503
|
-
const data = mergedData.value
|
|
504
|
-
const defaultSlot = slots.default
|
|
505
|
-
|
|
506
|
-
if (!defaultSlot) {
|
|
507
|
-
return children
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
for (let i = start.value; i <= end.value; i += 1) {
|
|
511
|
-
const item = data[i]
|
|
512
|
-
const key = getKey(item)
|
|
513
|
-
// Call the slot function with item, index, and props
|
|
514
|
-
const nodes = defaultSlot({
|
|
515
|
-
item,
|
|
516
|
-
index: i,
|
|
517
|
-
style: {},
|
|
518
|
-
offsetX: offsetLeft.value,
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
// Wrap each node in Item component
|
|
522
|
-
const node = Array.isArray(nodes) ? nodes[0] : nodes
|
|
523
|
-
if (node) {
|
|
524
|
-
children.push(
|
|
525
|
-
<Item key={key} setRef={ele => setInstanceRef(item, ele)}>
|
|
526
|
-
{node}
|
|
527
|
-
</Item>,
|
|
528
|
-
)
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return children
|
|
533
|
-
}
|
|
534
|
-
const componentStyle: CSSProperties = {}
|
|
535
|
-
if (props.height) {
|
|
536
|
-
componentStyle[props.fullHeight ? 'height' : 'maxHeight'] = `${props.height}px`
|
|
537
|
-
Object.assign(componentStyle, ScrollStyle)
|
|
538
|
-
|
|
539
|
-
if (useVirtual.value) {
|
|
540
|
-
componentStyle.overflowY = 'hidden'
|
|
541
|
-
|
|
542
|
-
if (horizontalRange.value > 0) {
|
|
543
|
-
componentStyle.overflowX = 'hidden'
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (scrollMoving.value) {
|
|
547
|
-
componentStyle.pointerEvents = 'none'
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const extraContent = props.extraRender?.({
|
|
553
|
-
start: start.value,
|
|
554
|
-
end: end.value,
|
|
555
|
-
virtual: inVirtual.value,
|
|
556
|
-
offsetX: offsetLeft.value,
|
|
557
|
-
offsetY: fillerOffset.value || 0,
|
|
558
|
-
rtl: isRTL.value,
|
|
559
|
-
getSize: getSize.value,
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
const Component = props.component as any
|
|
563
|
-
|
|
564
|
-
return (
|
|
565
|
-
<div
|
|
566
|
-
ref={containerRef}
|
|
567
|
-
{...pureAttrs(attrs)}
|
|
568
|
-
style={{ position: 'relative', ...(attrs.style as CSSProperties) }}
|
|
569
|
-
dir={isRTL.value ? 'rtl' : undefined}
|
|
570
|
-
class={[
|
|
571
|
-
props.prefixCls,
|
|
572
|
-
{ [`${props.prefixCls}-rtl`]: isRTL.value },
|
|
573
|
-
attrs.class,
|
|
574
|
-
]}
|
|
575
|
-
>
|
|
576
|
-
<ResizeObserver onResize={onHolderResize}>
|
|
577
|
-
<Component
|
|
578
|
-
class={`${props.prefixCls}-holder`}
|
|
579
|
-
style={componentStyle}
|
|
580
|
-
ref={componentRef}
|
|
581
|
-
onScroll={onFallbackScroll}
|
|
582
|
-
onWheel={onWheel}
|
|
583
|
-
onMouseenter={delayHideScrollBar}
|
|
584
|
-
>
|
|
585
|
-
<Filler
|
|
586
|
-
prefixCls={props.prefixCls}
|
|
587
|
-
height={scrollHeight.value}
|
|
588
|
-
offsetX={offsetLeft.value}
|
|
589
|
-
offsetY={fillerOffset.value}
|
|
590
|
-
scrollWidth={contentScrollWidth.value}
|
|
591
|
-
onInnerResize={collectHeight}
|
|
592
|
-
ref={fillerInnerRef}
|
|
593
|
-
innerProps={props.innerProps}
|
|
594
|
-
rtl={isRTL.value}
|
|
595
|
-
extra={extraContent}
|
|
596
|
-
>
|
|
597
|
-
{renderChildren()}
|
|
598
|
-
</Filler>
|
|
599
|
-
</Component>
|
|
600
|
-
</ResizeObserver>
|
|
601
|
-
|
|
602
|
-
{inVirtual.value && scrollHeight.value > (props.height || 0) && (
|
|
603
|
-
<ScrollBar
|
|
604
|
-
ref={verticalScrollBarRef}
|
|
605
|
-
prefixCls={props.prefixCls}
|
|
606
|
-
scrollOffset={offsetTop.value}
|
|
607
|
-
scrollRange={scrollHeight.value}
|
|
608
|
-
rtl={isRTL.value}
|
|
609
|
-
onScroll={onScrollBar}
|
|
610
|
-
onStartMove={onScrollbarStartMove}
|
|
611
|
-
onStopMove={onScrollbarStopMove}
|
|
612
|
-
spinSize={verticalScrollBarSpinSize.value}
|
|
613
|
-
containerSize={size.value.height}
|
|
614
|
-
showScrollBar={props.showScrollBar}
|
|
615
|
-
style={(props.styles as any)?.verticalScrollBar}
|
|
616
|
-
thumbStyle={(props.styles as any)?.verticalScrollBarThumb}
|
|
617
|
-
/>
|
|
618
|
-
)}
|
|
619
|
-
|
|
620
|
-
{inVirtual.value && contentScrollWidth.value > size.value.width && (
|
|
621
|
-
<ScrollBar
|
|
622
|
-
ref={horizontalScrollBarRef}
|
|
623
|
-
prefixCls={props.prefixCls}
|
|
624
|
-
scrollOffset={offsetLeft.value}
|
|
625
|
-
scrollRange={contentScrollWidth.value}
|
|
626
|
-
rtl={isRTL.value}
|
|
627
|
-
onScroll={onScrollBar}
|
|
628
|
-
onStartMove={onScrollbarStartMove}
|
|
629
|
-
onStopMove={onScrollbarStopMove}
|
|
630
|
-
spinSize={horizontalScrollBarSpinSize.value}
|
|
631
|
-
containerSize={size.value.width}
|
|
632
|
-
horizontal
|
|
633
|
-
showScrollBar={props.showScrollBar}
|
|
634
|
-
style={(props.styles as any)?.horizontalScrollBar}
|
|
635
|
-
thumbStyle={(props.styles as any)?.horizontalScrollBarThumb}
|
|
636
|
-
/>
|
|
637
|
-
)}
|
|
638
|
-
</div>
|
|
639
|
-
)
|
|
640
|
-
}
|
|
641
|
-
},
|
|
642
|
-
})
|