@indielayer/ui 1.16.0 → 1.18.0
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/README.md +2 -2
- package/docs/assets/css/tailwind.css +6 -0
- package/docs/components/common/CodePreview.vue +14 -9
- package/docs/components/common/DocsFeatures.vue +41 -0
- package/docs/components/common/DocsHero.vue +216 -0
- package/docs/components/common/DocumentPage.vue +99 -112
- package/docs/components/common/ExampleBlocks.vue +157 -0
- package/docs/components/menu/DocsMenu.vue +3 -0
- package/docs/components/toolbar/Toolbar.vue +11 -2
- package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
- package/docs/components/toolbar/ToolbarSearch.vue +59 -62
- package/docs/composables/useDocMeta.ts +47 -0
- package/docs/icons.ts +28 -0
- package/docs/layouts/default.vue +1 -3
- package/docs/layouts/simple.vue +3 -1
- package/docs/main.ts +5 -0
- package/docs/pages/colors.vue +56 -47
- package/docs/pages/component/infiniteLoader/composable.vue +168 -0
- package/docs/pages/component/infiniteLoader/index.vue +36 -0
- package/docs/pages/component/infiniteLoader/usage.vue +161 -0
- package/docs/pages/component/select/size.vue +1 -1
- package/docs/pages/component/select/usage.vue +14 -7
- package/docs/pages/component/virtualGrid/index.vue +29 -0
- package/docs/pages/component/virtualGrid/usage.vue +20 -0
- package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
- package/docs/pages/component/virtualList/index.vue +36 -0
- package/docs/pages/component/virtualList/usage.vue +17 -0
- package/docs/pages/error.vue +5 -3
- package/docs/pages/icons.vue +64 -54
- package/docs/pages/index.vue +93 -82
- package/docs/pages/typography.vue +38 -28
- package/docs/router/index.ts +31 -3
- package/docs/search/components.json +1 -1
- package/docs/search/index.json +1 -0
- package/lib/components/container/theme/Container.base.theme.js +1 -1
- package/lib/components/divider/theme/Divider.base.theme.js +1 -1
- package/lib/components/input/Input.vue.js +23 -24
- package/lib/components/select/Select.vue.d.ts +16 -27
- package/lib/components/select/Select.vue.js +452 -345
- package/lib/components/table/Table.vue.js +1 -1
- package/lib/composables/useVirtualList.d.ts +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +88 -76
- package/lib/index.umd.js +4 -4
- package/lib/install.js +15 -7
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
- package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
- package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
- package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
- package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
- package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
- package/lib/virtual/components/virtualList/VirtualList.vue.js +159 -0
- package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
- package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
- package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
- package/lib/virtual/components/virtualList/types.d.ts +115 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.js +68 -0
- package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
- package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
- package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
- package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
- package/lib/virtual/core/createCachedBounds.d.ts +6 -0
- package/lib/virtual/core/createCachedBounds.js +55 -0
- package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
- package/lib/virtual/core/getEstimatedSize.js +22 -0
- package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
- package/lib/virtual/core/getOffsetForIndex.js +40 -0
- package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
- package/lib/virtual/core/getStartStopIndices.js +31 -0
- package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
- package/lib/virtual/core/types.d.ts +11 -0
- package/lib/virtual/core/useCachedBounds.d.ts +7 -0
- package/lib/virtual/core/useCachedBounds.js +18 -0
- package/lib/virtual/core/useIsRtl.d.ts +2 -0
- package/lib/virtual/core/useIsRtl.js +15 -0
- package/lib/virtual/core/useItemSize.d.ts +5 -0
- package/lib/virtual/core/useItemSize.js +27 -0
- package/lib/virtual/core/useVirtualizer.d.ts +33 -0
- package/lib/virtual/core/useVirtualizer.js +171 -0
- package/lib/virtual/index.d.ts +9 -0
- package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
- package/lib/virtual/types.d.ts +2 -0
- package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
- package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
- package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
- package/lib/virtual/utils/assert.d.ts +1 -0
- package/lib/virtual/utils/assert.js +7 -0
- package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
- package/lib/virtual/utils/getRTLOffsetType.js +13 -0
- package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
- package/lib/virtual/utils/getScrollbarSize.js +11 -0
- package/lib/virtual/utils/isRtl.d.ts +1 -0
- package/lib/virtual/utils/isRtl.js +12 -0
- package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
- package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
- package/lib/virtual/utils/shallowCompare.d.ts +1 -0
- package/lib/virtual/utils/shallowCompare.js +14 -0
- package/package.json +8 -3
- package/src/components/container/theme/Container.base.theme.ts +1 -1
- package/src/components/divider/theme/Divider.base.theme.ts +1 -1
- package/src/components/input/Input.vue +1 -2
- package/src/components/select/Select.vue +97 -20
- package/src/components/table/Table.vue +1 -1
- package/src/composables/useVirtualList.ts +1 -1
- package/src/index.ts +1 -0
- package/src/install.ts +9 -3
- package/src/version.ts +1 -1
- package/src/virtual/README.md +285 -0
- package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
- package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
- package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
- package/src/virtual/components/virtualGrid/types.ts +160 -0
- package/src/virtual/components/virtualList/VirtualList.test.ts +164 -0
- package/src/virtual/components/virtualList/VirtualList.vue +227 -0
- package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
- package/src/virtual/components/virtualList/types.ts +127 -0
- package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +197 -0
- package/src/virtual/components/virtualList/useDynamicRowHeight.ts +149 -0
- package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
- package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
- package/src/virtual/composables/infinite-loader/types.ts +36 -0
- package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
- package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
- package/src/virtual/core/createCachedBounds.ts +72 -0
- package/src/virtual/core/getEstimatedSize.ts +29 -0
- package/src/virtual/core/getOffsetForIndex.ts +90 -0
- package/src/virtual/core/getStartStopIndices.test.ts +45 -0
- package/src/virtual/core/getStartStopIndices.ts +71 -0
- package/src/virtual/core/types.ts +17 -0
- package/src/virtual/core/useCachedBounds.ts +21 -0
- package/src/virtual/core/useIsRtl.ts +25 -0
- package/src/virtual/core/useItemSize.ts +34 -0
- package/src/virtual/core/useVirtualizer.ts +294 -0
- package/src/virtual/index.ts +25 -0
- package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
- package/src/virtual/types.ts +3 -0
- package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
- package/src/virtual/utils/areArraysEqual.ts +13 -0
- package/src/virtual/utils/assert.ts +10 -0
- package/src/virtual/utils/getRTLOffsetType.ts +51 -0
- package/src/virtual/utils/getScrollbarSize.ts +24 -0
- package/src/virtual/utils/isRtl.ts +13 -0
- package/src/virtual/utils/parseNumericStyleValue.ts +21 -0
- package/src/virtual/utils/shallowCompare.ts +29 -0
- package/volar.d.ts +3 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assert } from '../utils/assert'
|
|
2
|
+
import type { SizeFunction } from './types'
|
|
3
|
+
|
|
4
|
+
export function useItemSize<Props extends object>({
|
|
5
|
+
containerSize,
|
|
6
|
+
itemSize: itemSizeProp,
|
|
7
|
+
}: {
|
|
8
|
+
containerSize: number;
|
|
9
|
+
itemSize: number | string | SizeFunction<Props>;
|
|
10
|
+
}) {
|
|
11
|
+
let itemSize: number | SizeFunction<Props>
|
|
12
|
+
|
|
13
|
+
switch (typeof itemSizeProp) {
|
|
14
|
+
case 'string': {
|
|
15
|
+
assert(
|
|
16
|
+
itemSizeProp.endsWith('%'),
|
|
17
|
+
`Invalid item size: "${itemSizeProp}"; string values must be percentages (e.g. "100%")`,
|
|
18
|
+
)
|
|
19
|
+
assert(
|
|
20
|
+
containerSize !== undefined,
|
|
21
|
+
'Container size must be defined if a percentage item size is specified',
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
itemSize = (containerSize * parseInt(itemSizeProp)) / 100
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
default: {
|
|
28
|
+
itemSize = itemSizeProp
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return itemSize
|
|
34
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
isRef,
|
|
4
|
+
ref,
|
|
5
|
+
watch,
|
|
6
|
+
type ComputedRef,
|
|
7
|
+
type CSSProperties,
|
|
8
|
+
type Ref,
|
|
9
|
+
} from 'vue'
|
|
10
|
+
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
|
11
|
+
import type { Align } from '../types'
|
|
12
|
+
import { parseNumericStyleValue } from '../utils/parseNumericStyleValue'
|
|
13
|
+
import { adjustScrollOffsetForRtl } from '../utils/adjustScrollOffsetForRtl'
|
|
14
|
+
import { shallowCompare } from '../utils/shallowCompare'
|
|
15
|
+
import { getEstimatedSize as getEstimatedSizeUtil } from './getEstimatedSize'
|
|
16
|
+
import { getOffsetForIndex } from './getOffsetForIndex'
|
|
17
|
+
import { getStartStopIndices as getStartStopIndicesUtil } from './getStartStopIndices'
|
|
18
|
+
import type { Direction, SizeFunction } from './types'
|
|
19
|
+
import { useCachedBounds } from './useCachedBounds'
|
|
20
|
+
import { useItemSize } from './useItemSize'
|
|
21
|
+
|
|
22
|
+
export function useVirtualizer<Props extends object>({
|
|
23
|
+
containerElement,
|
|
24
|
+
containerStyle,
|
|
25
|
+
defaultContainerSize = 0,
|
|
26
|
+
direction,
|
|
27
|
+
isRtl = false,
|
|
28
|
+
itemCount: itemCountInput,
|
|
29
|
+
itemProps: itemPropsInput,
|
|
30
|
+
itemSize: itemSizeProp,
|
|
31
|
+
onResize,
|
|
32
|
+
overscanCount,
|
|
33
|
+
}: {
|
|
34
|
+
containerElement: Ref<HTMLElement | null>;
|
|
35
|
+
containerStyle?: CSSProperties;
|
|
36
|
+
defaultContainerSize?: number;
|
|
37
|
+
direction: Direction;
|
|
38
|
+
isRtl?: boolean;
|
|
39
|
+
itemCount: number | Ref<number> | ComputedRef<number>;
|
|
40
|
+
itemProps: Props | ComputedRef<Props>;
|
|
41
|
+
itemSize: number | string | SizeFunction<Props> | Ref<number | string | SizeFunction<Props>> | ComputedRef<number | string | SizeFunction<Props>>;
|
|
42
|
+
onResize:
|
|
43
|
+
| ((
|
|
44
|
+
size: { height: number; width: number; },
|
|
45
|
+
prevSize: { height: number; width: number; }
|
|
46
|
+
) => void)
|
|
47
|
+
| undefined;
|
|
48
|
+
overscanCount: number;
|
|
49
|
+
}) {
|
|
50
|
+
const itemCount = computed(() => {
|
|
51
|
+
return isRef(itemCountInput) ? itemCountInput.value : itemCountInput
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const itemProps = computed(() => {
|
|
55
|
+
return isRef(itemPropsInput) ? itemPropsInput.value : itemPropsInput
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const itemSizeNormalized = computed(() => {
|
|
59
|
+
return isRef(itemSizeProp) ? itemSizeProp.value : itemSizeProp
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const styleHeight = computed(() => parseNumericStyleValue(containerStyle?.height))
|
|
63
|
+
const styleWidth = computed(() => parseNumericStyleValue(containerStyle?.width))
|
|
64
|
+
|
|
65
|
+
const sizeState = ref<{
|
|
66
|
+
height: number | undefined;
|
|
67
|
+
width: number | undefined;
|
|
68
|
+
}>({
|
|
69
|
+
height: direction === 'vertical' ? defaultContainerSize : undefined,
|
|
70
|
+
width: direction === 'horizontal' ? defaultContainerSize : undefined,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const observerDisabled = computed(
|
|
74
|
+
() =>
|
|
75
|
+
(direction === 'vertical' && styleHeight.value !== undefined) ||
|
|
76
|
+
(direction === 'horizontal' && styleWidth.value !== undefined),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
useResizeObserver(
|
|
80
|
+
containerElement,
|
|
81
|
+
(entries) => {
|
|
82
|
+
if (observerDisabled.value) return
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const { contentRect } = entry
|
|
86
|
+
const prevState = sizeState.value
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
prevState.height === contentRect.height &&
|
|
90
|
+
prevState.width === contentRect.width
|
|
91
|
+
) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
sizeState.value = {
|
|
96
|
+
height: contentRect.height,
|
|
97
|
+
width: contentRect.width,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const size = computed(() => ({
|
|
104
|
+
height: styleHeight.value ?? sizeState.value.height,
|
|
105
|
+
width: styleWidth.value ?? sizeState.value.width,
|
|
106
|
+
}))
|
|
107
|
+
|
|
108
|
+
const prevSize = ref<{ height: number; width: number; }>({
|
|
109
|
+
height: 0,
|
|
110
|
+
width: 0,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
watch(
|
|
114
|
+
size,
|
|
115
|
+
(newSize) => {
|
|
116
|
+
if (typeof onResize === 'function') {
|
|
117
|
+
const prev = prevSize.value
|
|
118
|
+
const height = newSize.height ?? 0
|
|
119
|
+
const width = newSize.width ?? 0
|
|
120
|
+
|
|
121
|
+
if (prev.height !== height || prev.width !== width) {
|
|
122
|
+
onResize({ height, width }, { ...prev })
|
|
123
|
+
|
|
124
|
+
prevSize.value = { height, width }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{ immediate: true },
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const containerSize = computed(() =>
|
|
132
|
+
direction === 'vertical'
|
|
133
|
+
? size.value.height ?? defaultContainerSize
|
|
134
|
+
: size.value.width ?? defaultContainerSize,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const itemSize = computed(() =>
|
|
138
|
+
useItemSize({ containerSize: containerSize.value, itemSize: itemSizeNormalized.value }),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const cachedBounds = computed(() =>
|
|
142
|
+
useCachedBounds({
|
|
143
|
+
itemCount: itemCount.value,
|
|
144
|
+
itemProps: itemProps.value,
|
|
145
|
+
itemSize: itemSize.value,
|
|
146
|
+
}).value,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const getCellBounds = (index: number) => cachedBounds.value.get(index)
|
|
150
|
+
|
|
151
|
+
const indices = ref<{
|
|
152
|
+
startIndexVisible: number;
|
|
153
|
+
stopIndexVisible: number;
|
|
154
|
+
startIndexOverscan: number;
|
|
155
|
+
stopIndexOverscan: number;
|
|
156
|
+
}>(
|
|
157
|
+
getStartStopIndicesUtil({
|
|
158
|
+
cachedBounds: cachedBounds.value,
|
|
159
|
+
containerScrollOffset: 0,
|
|
160
|
+
containerSize: containerSize.value,
|
|
161
|
+
itemCount: itemCount.value,
|
|
162
|
+
overscanCount,
|
|
163
|
+
}),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const safeIndices = computed(() => {
|
|
167
|
+
const ind = indices.value
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
startIndexVisible: Math.min(itemCount.value - 1, ind.startIndexVisible),
|
|
171
|
+
startIndexOverscan: Math.min(itemCount.value - 1, ind.startIndexOverscan),
|
|
172
|
+
stopIndexVisible: Math.min(itemCount.value - 1, ind.stopIndexVisible),
|
|
173
|
+
stopIndexOverscan: Math.min(itemCount.value - 1, ind.stopIndexOverscan),
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const getEstimatedSize = computed(() =>
|
|
178
|
+
getEstimatedSizeUtil({
|
|
179
|
+
cachedBounds: cachedBounds.value,
|
|
180
|
+
itemCount: itemCount.value,
|
|
181
|
+
itemSize: itemSize.value,
|
|
182
|
+
}),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const getStartStopIndices = (scrollOffset: number) => {
|
|
186
|
+
const containerScrollOffset = adjustScrollOffsetForRtl({
|
|
187
|
+
containerElement: containerElement.value,
|
|
188
|
+
direction,
|
|
189
|
+
isRtl,
|
|
190
|
+
scrollOffset,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
return getStartStopIndicesUtil({
|
|
194
|
+
cachedBounds: cachedBounds.value,
|
|
195
|
+
containerScrollOffset,
|
|
196
|
+
containerSize: containerSize.value,
|
|
197
|
+
itemCount: itemCount.value,
|
|
198
|
+
overscanCount,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
watch(
|
|
203
|
+
[containerElement, () => direction, () => cachedBounds.value],
|
|
204
|
+
([el]) => {
|
|
205
|
+
const scrollOffset =
|
|
206
|
+
(direction === 'vertical' ? el?.scrollTop : el?.scrollLeft) ?? 0
|
|
207
|
+
|
|
208
|
+
indices.value = getStartStopIndices(scrollOffset)
|
|
209
|
+
},
|
|
210
|
+
{ immediate: true },
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const onScroll = () => {
|
|
214
|
+
const el = containerElement.value
|
|
215
|
+
|
|
216
|
+
if (!el) return
|
|
217
|
+
|
|
218
|
+
const { scrollLeft, scrollTop } = el
|
|
219
|
+
|
|
220
|
+
const scrollOffset = adjustScrollOffsetForRtl({
|
|
221
|
+
containerElement: el,
|
|
222
|
+
direction,
|
|
223
|
+
isRtl,
|
|
224
|
+
scrollOffset: direction === 'vertical' ? scrollTop : scrollLeft,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const next = getStartStopIndicesUtil({
|
|
228
|
+
cachedBounds: cachedBounds.value,
|
|
229
|
+
containerScrollOffset: scrollOffset,
|
|
230
|
+
containerSize: containerSize.value,
|
|
231
|
+
itemCount: itemCount.value,
|
|
232
|
+
overscanCount,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
if (!shallowCompare(next, indices.value)) {
|
|
236
|
+
indices.value = next
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
useEventListener(containerElement, 'scroll', onScroll)
|
|
241
|
+
|
|
242
|
+
const scrollToIndex = ({
|
|
243
|
+
align = 'auto',
|
|
244
|
+
containerScrollOffset,
|
|
245
|
+
index,
|
|
246
|
+
}: {
|
|
247
|
+
align?: Align;
|
|
248
|
+
containerScrollOffset: number;
|
|
249
|
+
index: number;
|
|
250
|
+
}) => {
|
|
251
|
+
let scrollOffset = getOffsetForIndex({
|
|
252
|
+
align,
|
|
253
|
+
cachedBounds: cachedBounds.value,
|
|
254
|
+
containerScrollOffset,
|
|
255
|
+
containerSize: containerSize.value,
|
|
256
|
+
index,
|
|
257
|
+
itemCount: itemCount.value,
|
|
258
|
+
itemSize: itemSize.value,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const el = containerElement.value
|
|
262
|
+
|
|
263
|
+
if (el) {
|
|
264
|
+
scrollOffset = adjustScrollOffsetForRtl({
|
|
265
|
+
containerElement: el,
|
|
266
|
+
direction,
|
|
267
|
+
isRtl,
|
|
268
|
+
scrollOffset,
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
if (typeof el.scrollTo !== 'function') {
|
|
272
|
+
const next = getStartStopIndices(scrollOffset)
|
|
273
|
+
|
|
274
|
+
if (!shallowCompare(indices.value, next)) {
|
|
275
|
+
indices.value = next
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return scrollOffset
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return undefined
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
getCellBounds,
|
|
287
|
+
getEstimatedSize,
|
|
288
|
+
scrollToIndex,
|
|
289
|
+
startIndexOverscan: computed(() => safeIndices.value.startIndexOverscan),
|
|
290
|
+
startIndexVisible: computed(() => safeIndices.value.startIndexVisible),
|
|
291
|
+
stopIndexOverscan: computed(() => safeIndices.value.stopIndexOverscan),
|
|
292
|
+
stopIndexVisible: computed(() => safeIndices.value.stopIndexVisible),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { default as XVirtualGrid } from './components/virtualGrid/VirtualGrid.vue'
|
|
2
|
+
export type {
|
|
3
|
+
CellSlotProps,
|
|
4
|
+
VirtualGridImperativeAPI,
|
|
5
|
+
VirtualGridProps,
|
|
6
|
+
} from './components/virtualGrid/types'
|
|
7
|
+
|
|
8
|
+
export { default as XVirtualList } from './components/virtualList/VirtualList.vue'
|
|
9
|
+
export { useDynamicRowHeight } from './components/virtualList/useDynamicRowHeight'
|
|
10
|
+
export type {
|
|
11
|
+
DynamicRowHeight,
|
|
12
|
+
VirtualListImperativeAPI,
|
|
13
|
+
VirtualListProps,
|
|
14
|
+
RowSlotProps,
|
|
15
|
+
} from './components/virtualList/types'
|
|
16
|
+
|
|
17
|
+
export { default as XInfiniteLoader } from './components/infiniteLoader/InfiniteLoader.vue'
|
|
18
|
+
export { useInfiniteLoader } from './composables/infinite-loader/useInfiniteLoader'
|
|
19
|
+
export type {
|
|
20
|
+
Indices,
|
|
21
|
+
OnRowsRendered,
|
|
22
|
+
InfiniteLoaderProps,
|
|
23
|
+
} from './composables/infinite-loader/types'
|
|
24
|
+
|
|
25
|
+
export { getScrollbarSize } from './utils/getScrollbarSize'
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import EventEmitter from 'node:events'
|
|
2
|
+
|
|
3
|
+
type GetDOMRect = (element: HTMLElement) => DOMRectReadOnly | undefined | void;
|
|
4
|
+
|
|
5
|
+
const emitter = new EventEmitter()
|
|
6
|
+
|
|
7
|
+
emitter.setMaxListeners(100)
|
|
8
|
+
|
|
9
|
+
const elementToDOMRect = new Map<HTMLElement, DOMRect>()
|
|
10
|
+
|
|
11
|
+
let defaultDomRect: DOMRectReadOnly = new DOMRect(0, 0, 0, 0)
|
|
12
|
+
let disabled: boolean = false
|
|
13
|
+
let getDOMRect: GetDOMRect | undefined = undefined
|
|
14
|
+
|
|
15
|
+
export function disableResizeObserverForCurrentTest() {
|
|
16
|
+
disabled = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function setDefaultElementSize({
|
|
20
|
+
height,
|
|
21
|
+
width,
|
|
22
|
+
}: {
|
|
23
|
+
height: number;
|
|
24
|
+
width: number;
|
|
25
|
+
}) {
|
|
26
|
+
defaultDomRect = new DOMRect(0, 0, width, height)
|
|
27
|
+
|
|
28
|
+
emitter.emit('change')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function setElementSizeFunction(value: GetDOMRect) {
|
|
32
|
+
getDOMRect = value
|
|
33
|
+
|
|
34
|
+
emitter.emit('change')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setElementSize({
|
|
38
|
+
element,
|
|
39
|
+
height,
|
|
40
|
+
width,
|
|
41
|
+
}: {
|
|
42
|
+
element: HTMLElement;
|
|
43
|
+
height: number;
|
|
44
|
+
width: number;
|
|
45
|
+
}) {
|
|
46
|
+
elementToDOMRect.set(element, new DOMRect(0, 0, width, height))
|
|
47
|
+
|
|
48
|
+
emitter.emit('change', element)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function simulateUnsupportedEnvironmentForTest() {
|
|
52
|
+
// @ts-expect-error Simulate API being unsupported
|
|
53
|
+
window.ResizeObserver = null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function mockResizeObserver() {
|
|
57
|
+
disabled = false
|
|
58
|
+
|
|
59
|
+
const originalResizeObserver = window.ResizeObserver
|
|
60
|
+
|
|
61
|
+
window.ResizeObserver = MockResizeObserver
|
|
62
|
+
|
|
63
|
+
return function unmockResizeObserver() {
|
|
64
|
+
window.ResizeObserver = originalResizeObserver
|
|
65
|
+
|
|
66
|
+
defaultDomRect = new DOMRect(0, 0, 0, 0)
|
|
67
|
+
disabled = false
|
|
68
|
+
getDOMRect = undefined
|
|
69
|
+
|
|
70
|
+
elementToDOMRect.clear()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class MockResizeObserver implements ResizeObserver {
|
|
75
|
+
readonly #callback: ResizeObserverCallback
|
|
76
|
+
#disconnected: boolean = false
|
|
77
|
+
#elements: Set<HTMLElement> = new Set()
|
|
78
|
+
|
|
79
|
+
constructor(callback: ResizeObserverCallback) {
|
|
80
|
+
this.#callback = callback
|
|
81
|
+
|
|
82
|
+
emitter.addListener('change', this.#onChange)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
observe(element: HTMLElement) {
|
|
86
|
+
if (this.#disconnected) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.#elements.add(element)
|
|
91
|
+
this.#notify([element])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
unobserve(element: HTMLElement) {
|
|
95
|
+
this.#elements.delete(element)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
disconnect() {
|
|
99
|
+
this.#disconnected = true
|
|
100
|
+
this.#elements.clear()
|
|
101
|
+
|
|
102
|
+
emitter.removeListener('change', this.#onChange)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#notify(elements: HTMLElement[]) {
|
|
106
|
+
if (disabled) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const entries = elements.map((element) => {
|
|
111
|
+
const computedStyle = window.getComputedStyle(element)
|
|
112
|
+
const writingMode = computedStyle.writingMode
|
|
113
|
+
|
|
114
|
+
let contentRect: DOMRectReadOnly =
|
|
115
|
+
elementToDOMRect.get(element) ?? defaultDomRect
|
|
116
|
+
|
|
117
|
+
if (getDOMRect) {
|
|
118
|
+
const contentRectOverride = getDOMRect(element)
|
|
119
|
+
|
|
120
|
+
if (contentRectOverride) {
|
|
121
|
+
contentRect = contentRectOverride
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let blockSize = 0
|
|
126
|
+
let inlineSize = 0
|
|
127
|
+
|
|
128
|
+
if (writingMode.includes('vertical')) {
|
|
129
|
+
blockSize = contentRect.width
|
|
130
|
+
inlineSize = contentRect.height
|
|
131
|
+
} else {
|
|
132
|
+
blockSize = contentRect.height
|
|
133
|
+
inlineSize = contentRect.width
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
borderBoxSize: [
|
|
138
|
+
{
|
|
139
|
+
blockSize,
|
|
140
|
+
inlineSize,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
contentBoxSize: [],
|
|
144
|
+
contentRect,
|
|
145
|
+
devicePixelContentBoxSize: [],
|
|
146
|
+
target: element,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
this.#callback(entries, this)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#onChange = (target?: HTMLElement) => {
|
|
154
|
+
if (target) {
|
|
155
|
+
if (this.#elements.has(target)) {
|
|
156
|
+
this.#notify([target])
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
this.#notify(Array.from(this.#elements))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Direction } from '../core/types'
|
|
2
|
+
import { getRTLOffsetType } from './getRTLOffsetType'
|
|
3
|
+
|
|
4
|
+
export function adjustScrollOffsetForRtl({
|
|
5
|
+
containerElement,
|
|
6
|
+
direction,
|
|
7
|
+
isRtl,
|
|
8
|
+
scrollOffset,
|
|
9
|
+
}: {
|
|
10
|
+
containerElement: HTMLElement | null;
|
|
11
|
+
direction: Direction;
|
|
12
|
+
isRtl: boolean;
|
|
13
|
+
scrollOffset: number;
|
|
14
|
+
}) {
|
|
15
|
+
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
|
16
|
+
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
|
|
17
|
+
// So we need to determine which browser behavior we're dealing with, and mimic it.
|
|
18
|
+
if (direction === 'horizontal') {
|
|
19
|
+
if (isRtl) {
|
|
20
|
+
switch (getRTLOffsetType()) {
|
|
21
|
+
case 'negative': {
|
|
22
|
+
return -scrollOffset
|
|
23
|
+
}
|
|
24
|
+
case 'positive-descending': {
|
|
25
|
+
if (containerElement) {
|
|
26
|
+
const { clientWidth, scrollLeft, scrollWidth } = containerElement
|
|
27
|
+
|
|
28
|
+
return scrollWidth - clientWidth - scrollLeft
|
|
29
|
+
}
|
|
30
|
+
break
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return scrollOffset
|
|
37
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type RTLOffsetType =
|
|
2
|
+
| 'negative'
|
|
3
|
+
| 'positive-descending'
|
|
4
|
+
| 'positive-ascending';
|
|
5
|
+
|
|
6
|
+
let cachedRTLResult: RTLOffsetType | null = null
|
|
7
|
+
|
|
8
|
+
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
|
|
9
|
+
// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left).
|
|
10
|
+
// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives.
|
|
11
|
+
// The safest way to check this is to intentionally set a negative offset,
|
|
12
|
+
// and then verify that the subsequent "scroll" event matches the negative offset.
|
|
13
|
+
// If it does not match, then we can assume a non-standard RTL scroll implementation.
|
|
14
|
+
export function getRTLOffsetType(recalculate: boolean = false): RTLOffsetType {
|
|
15
|
+
if (cachedRTLResult === null || recalculate) {
|
|
16
|
+
const outerDiv = document.createElement('div')
|
|
17
|
+
const outerStyle = outerDiv.style
|
|
18
|
+
|
|
19
|
+
outerStyle.width = '50px'
|
|
20
|
+
outerStyle.height = '50px'
|
|
21
|
+
outerStyle.overflow = 'scroll'
|
|
22
|
+
outerStyle.direction = 'rtl'
|
|
23
|
+
|
|
24
|
+
const innerDiv = document.createElement('div')
|
|
25
|
+
const innerStyle = innerDiv.style
|
|
26
|
+
|
|
27
|
+
innerStyle.width = '100px'
|
|
28
|
+
innerStyle.height = '100px'
|
|
29
|
+
|
|
30
|
+
outerDiv.appendChild(innerDiv)
|
|
31
|
+
|
|
32
|
+
document.body.appendChild(outerDiv)
|
|
33
|
+
|
|
34
|
+
if (outerDiv.scrollLeft > 0) {
|
|
35
|
+
cachedRTLResult = 'positive-descending'
|
|
36
|
+
} else {
|
|
37
|
+
outerDiv.scrollLeft = 1
|
|
38
|
+
if (outerDiv.scrollLeft === 0) {
|
|
39
|
+
cachedRTLResult = 'negative'
|
|
40
|
+
} else {
|
|
41
|
+
cachedRTLResult = 'positive-ascending'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
document.body.removeChild(outerDiv)
|
|
46
|
+
|
|
47
|
+
return cachedRTLResult
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return cachedRTLResult
|
|
51
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let size: number = -1
|
|
2
|
+
|
|
3
|
+
export function getScrollbarSize(recalculate: boolean = false): number {
|
|
4
|
+
if (size === -1 || recalculate) {
|
|
5
|
+
const div = document.createElement('div')
|
|
6
|
+
const style = div.style
|
|
7
|
+
|
|
8
|
+
style.width = '50px'
|
|
9
|
+
style.height = '50px'
|
|
10
|
+
style.overflow = 'scroll'
|
|
11
|
+
|
|
12
|
+
document.body.appendChild(div)
|
|
13
|
+
|
|
14
|
+
size = div.offsetWidth - div.clientWidth
|
|
15
|
+
|
|
16
|
+
document.body.removeChild(div)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return size
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function setScrollbarSizeForTests(value: number) {
|
|
23
|
+
size = value
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function isRtl(element: HTMLElement) {
|
|
2
|
+
let currentElement: HTMLElement | null = element
|
|
3
|
+
|
|
4
|
+
while (currentElement) {
|
|
5
|
+
if (currentElement.dir) {
|
|
6
|
+
return currentElement.dir === 'rtl'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
currentElement = currentElement.parentElement
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CSSProperties } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function parseNumericStyleValue(
|
|
4
|
+
value: CSSProperties['height'],
|
|
5
|
+
): number | undefined {
|
|
6
|
+
if (value !== undefined) {
|
|
7
|
+
switch (typeof value) {
|
|
8
|
+
case 'number': {
|
|
9
|
+
return value
|
|
10
|
+
}
|
|
11
|
+
case 'string': {
|
|
12
|
+
if (value.endsWith('px')) {
|
|
13
|
+
return parseFloat(value)
|
|
14
|
+
}
|
|
15
|
+
break
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return undefined
|
|
21
|
+
}
|