@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,96 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { h } from 'vue'
|
|
4
|
+
import InfiniteLoader from './InfiniteLoader.vue'
|
|
5
|
+
import type { InfiniteLoaderProps } from '../../composables/infinite-loader/types'
|
|
6
|
+
|
|
7
|
+
interface InfiniteLoaderSlotProps {
|
|
8
|
+
onRowsRendered: (props: { startIndex: number; stopIndex: number; }) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('InfiniteLoader', () => {
|
|
12
|
+
test('should render slot with onRowsRendered callback', () => {
|
|
13
|
+
const isRowLoaded = vi.fn(() => true)
|
|
14
|
+
const loadMoreRows = vi.fn(() => Promise.resolve())
|
|
15
|
+
|
|
16
|
+
const wrapper = mount(InfiniteLoader, {
|
|
17
|
+
props: {
|
|
18
|
+
isRowLoaded,
|
|
19
|
+
loadMoreRows,
|
|
20
|
+
rowCount: 10,
|
|
21
|
+
} as InfiniteLoaderProps,
|
|
22
|
+
slots: {
|
|
23
|
+
default: (slotProps: InfiniteLoaderSlotProps) => {
|
|
24
|
+
return h('div', { 'data-testid': 'slot-content' }, [
|
|
25
|
+
`Callback type: ${typeof slotProps.onRowsRendered}`,
|
|
26
|
+
])
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(wrapper.html()).toContain('Callback type: function')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('should pass through onRowsRendered callback that triggers loadMoreRows', () => {
|
|
35
|
+
const loadMoreRows = vi.fn(() => Promise.resolve())
|
|
36
|
+
|
|
37
|
+
let capturedCallback: ((props: { startIndex: number; stopIndex: number; }) => void) | null = null
|
|
38
|
+
|
|
39
|
+
mount(InfiniteLoader, {
|
|
40
|
+
props: {
|
|
41
|
+
isRowLoaded: (index) => index <= 2,
|
|
42
|
+
loadMoreRows,
|
|
43
|
+
rowCount: 10,
|
|
44
|
+
threshold: 0, // Disable threshold for this test
|
|
45
|
+
minimumBatchSize: 0, // Disable minimum batch size for this test
|
|
46
|
+
} as InfiniteLoaderProps,
|
|
47
|
+
slots: {
|
|
48
|
+
default: (slotProps: InfiniteLoaderSlotProps) => {
|
|
49
|
+
capturedCallback = slotProps.onRowsRendered
|
|
50
|
+
|
|
51
|
+
return h('div')
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
expect(capturedCallback).toBeDefined()
|
|
57
|
+
expect(typeof capturedCallback).toBe('function')
|
|
58
|
+
|
|
59
|
+
// Call the callback if it is defined
|
|
60
|
+
if (capturedCallback) {
|
|
61
|
+
(capturedCallback as (props: { startIndex: number; stopIndex: number; }) => void)({ startIndex: 0, stopIndex: 5 })
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error('capturedCallback is null')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Should have triggered loadMoreRows for unloaded rows 3-5
|
|
67
|
+
expect(loadMoreRows).toHaveBeenCalled()
|
|
68
|
+
expect(loadMoreRows).toHaveBeenCalledWith(3, 5)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('should work with VList integration pattern', () => {
|
|
72
|
+
const loadMoreRows = vi.fn(() => Promise.resolve())
|
|
73
|
+
const loadedRows = new Set([0, 1, 2])
|
|
74
|
+
|
|
75
|
+
const wrapper = mount(InfiniteLoader, {
|
|
76
|
+
props: {
|
|
77
|
+
isRowLoaded: (index) => loadedRows.has(index),
|
|
78
|
+
loadMoreRows,
|
|
79
|
+
rowCount: 10,
|
|
80
|
+
minimumBatchSize: 5,
|
|
81
|
+
} as InfiniteLoaderProps,
|
|
82
|
+
slots: {
|
|
83
|
+
default: (slotProps: InfiniteLoaderSlotProps) => {
|
|
84
|
+
// Simulate VList calling onRowsRendered
|
|
85
|
+
slotProps.onRowsRendered({ startIndex: 0, stopIndex: 7 })
|
|
86
|
+
|
|
87
|
+
return h('div', 'List content')
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Should load rows 3-7 with minimum batch size of 5
|
|
93
|
+
expect(loadMoreRows).toHaveBeenCalled()
|
|
94
|
+
expect(wrapper.html()).toContain('List content')
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: 'XInfiniteLoader',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { useInfiniteLoader } from '../../composables/infinite-loader/useInfiniteLoader'
|
|
9
|
+
import type { InfiniteLoaderProps } from '../../composables/infinite-loader/types'
|
|
10
|
+
|
|
11
|
+
const props = defineProps<InfiniteLoaderProps>()
|
|
12
|
+
|
|
13
|
+
const { onRowsRendered } = useInfiniteLoader(props)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<slot :on-rows-rendered="onRowsRendered" ></slot>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default {
|
|
3
|
+
name: 'XVirtualGrid',
|
|
4
|
+
}
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { computed, ref, watch, type CSSProperties } from 'vue'
|
|
9
|
+
import { useIsRtl } from '../../core/useIsRtl'
|
|
10
|
+
import { useVirtualizer } from '../../core/useVirtualizer'
|
|
11
|
+
import type { Align } from '../../types'
|
|
12
|
+
import type { VirtualGridProps, VirtualGridImperativeAPI } from './types'
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<VirtualGridProps>(), {
|
|
15
|
+
defaultHeight: 0,
|
|
16
|
+
defaultWidth: 0,
|
|
17
|
+
overscanCount: 3,
|
|
18
|
+
tag: 'div',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const element = ref<HTMLDivElement | null>(null)
|
|
22
|
+
|
|
23
|
+
const cellProps = computed(() => props.cellProps || ({} as Record<string, unknown>))
|
|
24
|
+
|
|
25
|
+
const isRtl = useIsRtl(element, props.dir)
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
getCellBounds: getColumnBounds,
|
|
29
|
+
getEstimatedSize: getEstimatedWidth,
|
|
30
|
+
startIndexOverscan: columnStartIndexOverscan,
|
|
31
|
+
startIndexVisible: columnStartIndexVisible,
|
|
32
|
+
scrollToIndex: scrollToColumnIndex,
|
|
33
|
+
stopIndexOverscan: columnStopIndexOverscan,
|
|
34
|
+
stopIndexVisible: columnStopIndexVisible,
|
|
35
|
+
} = useVirtualizer({
|
|
36
|
+
containerElement: element,
|
|
37
|
+
containerStyle: props.style,
|
|
38
|
+
defaultContainerSize: props.defaultWidth,
|
|
39
|
+
direction: 'horizontal',
|
|
40
|
+
isRtl: isRtl.value,
|
|
41
|
+
itemCount: props.columnCount,
|
|
42
|
+
itemProps: cellProps.value,
|
|
43
|
+
itemSize: props.columnWidth,
|
|
44
|
+
onResize: props.onResize,
|
|
45
|
+
overscanCount: props.overscanCount,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
getCellBounds: getRowBounds,
|
|
50
|
+
getEstimatedSize: getEstimatedHeight,
|
|
51
|
+
startIndexOverscan: rowStartIndexOverscan,
|
|
52
|
+
startIndexVisible: rowStartIndexVisible,
|
|
53
|
+
scrollToIndex: scrollToRowIndex,
|
|
54
|
+
stopIndexOverscan: rowStopIndexOverscan,
|
|
55
|
+
stopIndexVisible: rowStopIndexVisible,
|
|
56
|
+
} = useVirtualizer({
|
|
57
|
+
containerElement: element,
|
|
58
|
+
containerStyle: props.style,
|
|
59
|
+
defaultContainerSize: props.defaultHeight,
|
|
60
|
+
direction: 'vertical',
|
|
61
|
+
itemCount: props.rowCount,
|
|
62
|
+
itemProps: cellProps.value,
|
|
63
|
+
itemSize: props.rowHeight,
|
|
64
|
+
onResize: props.onResize,
|
|
65
|
+
overscanCount: props.overscanCount,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Expose imperative API
|
|
69
|
+
defineExpose<VirtualGridImperativeAPI>({
|
|
70
|
+
get element() {
|
|
71
|
+
return element.value
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
scrollToCell({
|
|
75
|
+
behavior = 'auto',
|
|
76
|
+
columnAlign = 'auto',
|
|
77
|
+
columnIndex,
|
|
78
|
+
rowAlign = 'auto',
|
|
79
|
+
rowIndex,
|
|
80
|
+
}: {
|
|
81
|
+
behavior?: ScrollBehavior;
|
|
82
|
+
columnAlign?: Align;
|
|
83
|
+
columnIndex: number;
|
|
84
|
+
rowAlign?: Align;
|
|
85
|
+
rowIndex: number;
|
|
86
|
+
}) {
|
|
87
|
+
const left = scrollToColumnIndex({
|
|
88
|
+
align: columnAlign,
|
|
89
|
+
containerScrollOffset: element.value?.scrollLeft ?? 0,
|
|
90
|
+
index: columnIndex,
|
|
91
|
+
})
|
|
92
|
+
const top = scrollToRowIndex({
|
|
93
|
+
align: rowAlign,
|
|
94
|
+
containerScrollOffset: element.value?.scrollTop ?? 0,
|
|
95
|
+
index: rowIndex,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (typeof element.value?.scrollTo === 'function' && left !== undefined && top !== undefined) {
|
|
99
|
+
element.value.scrollTo({
|
|
100
|
+
behavior,
|
|
101
|
+
left,
|
|
102
|
+
top,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
scrollToColumn({
|
|
108
|
+
align = 'auto',
|
|
109
|
+
behavior = 'auto',
|
|
110
|
+
index,
|
|
111
|
+
}: {
|
|
112
|
+
align?: Align;
|
|
113
|
+
behavior?: ScrollBehavior;
|
|
114
|
+
index: number;
|
|
115
|
+
}) {
|
|
116
|
+
const left = scrollToColumnIndex({
|
|
117
|
+
align,
|
|
118
|
+
containerScrollOffset: element.value?.scrollLeft ?? 0,
|
|
119
|
+
index,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (typeof element.value?.scrollTo === 'function' && left !== undefined) {
|
|
123
|
+
element.value.scrollTo({
|
|
124
|
+
behavior,
|
|
125
|
+
left,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
scrollToRow({
|
|
131
|
+
align = 'auto',
|
|
132
|
+
behavior = 'auto',
|
|
133
|
+
index,
|
|
134
|
+
}: {
|
|
135
|
+
align?: Align;
|
|
136
|
+
behavior?: ScrollBehavior;
|
|
137
|
+
index: number;
|
|
138
|
+
}) {
|
|
139
|
+
const top = scrollToRowIndex({
|
|
140
|
+
align,
|
|
141
|
+
containerScrollOffset: element.value?.scrollTop ?? 0,
|
|
142
|
+
index,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (typeof element.value?.scrollTo === 'function' && top !== undefined) {
|
|
146
|
+
element.value.scrollTo({
|
|
147
|
+
behavior,
|
|
148
|
+
top,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Notify when visible cells change
|
|
155
|
+
watch(
|
|
156
|
+
[
|
|
157
|
+
columnStartIndexOverscan,
|
|
158
|
+
columnStartIndexVisible,
|
|
159
|
+
columnStopIndexOverscan,
|
|
160
|
+
columnStopIndexVisible,
|
|
161
|
+
rowStartIndexOverscan,
|
|
162
|
+
rowStartIndexVisible,
|
|
163
|
+
rowStopIndexOverscan,
|
|
164
|
+
rowStopIndexVisible,
|
|
165
|
+
],
|
|
166
|
+
([
|
|
167
|
+
colStartOverscan,
|
|
168
|
+
colStartVisible,
|
|
169
|
+
colStopOverscan,
|
|
170
|
+
colStopVisible,
|
|
171
|
+
rowStartOverscan,
|
|
172
|
+
rowStartVisible,
|
|
173
|
+
rowStopOverscan,
|
|
174
|
+
rowStopVisible,
|
|
175
|
+
]) => {
|
|
176
|
+
if (
|
|
177
|
+
colStartOverscan >= 0 &&
|
|
178
|
+
colStopOverscan >= 0 &&
|
|
179
|
+
rowStartOverscan >= 0 &&
|
|
180
|
+
rowStopOverscan >= 0 &&
|
|
181
|
+
props.onCellsRendered
|
|
182
|
+
) {
|
|
183
|
+
props.onCellsRendered(
|
|
184
|
+
{
|
|
185
|
+
columnStartIndex: colStartVisible,
|
|
186
|
+
columnStopIndex: colStopVisible,
|
|
187
|
+
rowStartIndex: rowStartVisible,
|
|
188
|
+
rowStopIndex: rowStopVisible,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
columnStartIndex: colStartOverscan,
|
|
192
|
+
columnStopIndex: colStopOverscan,
|
|
193
|
+
rowStartIndex: rowStartOverscan,
|
|
194
|
+
rowStopIndex: rowStopOverscan,
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
interface CellData {
|
|
202
|
+
key: number;
|
|
203
|
+
columnIndex: number;
|
|
204
|
+
rowIndex: number;
|
|
205
|
+
style: CSSProperties;
|
|
206
|
+
ariaAttributes: {
|
|
207
|
+
'aria-colindex': number;
|
|
208
|
+
role: 'gridcell';
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface RowData {
|
|
213
|
+
key: number;
|
|
214
|
+
rowIndex: number;
|
|
215
|
+
columns: CellData[];
|
|
216
|
+
ariaRowIndex: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Generate cells
|
|
220
|
+
const rows = computed(() => {
|
|
221
|
+
const result: RowData[] = []
|
|
222
|
+
|
|
223
|
+
if (props.columnCount > 0 && props.rowCount > 0) {
|
|
224
|
+
for (
|
|
225
|
+
let rowIndex = rowStartIndexOverscan.value;
|
|
226
|
+
rowIndex <= rowStopIndexOverscan.value;
|
|
227
|
+
rowIndex++
|
|
228
|
+
) {
|
|
229
|
+
const rowBounds = getRowBounds(rowIndex)
|
|
230
|
+
|
|
231
|
+
const columns: CellData[] = []
|
|
232
|
+
|
|
233
|
+
for (
|
|
234
|
+
let columnIndex = columnStartIndexOverscan.value;
|
|
235
|
+
columnIndex <= columnStopIndexOverscan.value;
|
|
236
|
+
columnIndex++
|
|
237
|
+
) {
|
|
238
|
+
const columnBounds = getColumnBounds(columnIndex)
|
|
239
|
+
|
|
240
|
+
const cellStyle: CSSProperties = {
|
|
241
|
+
position: 'absolute',
|
|
242
|
+
left: isRtl.value ? undefined : 0,
|
|
243
|
+
right: isRtl.value ? 0 : undefined,
|
|
244
|
+
transform: `translate(${isRtl.value ? -columnBounds.scrollOffset : columnBounds.scrollOffset}px, ${rowBounds.scrollOffset}px)`,
|
|
245
|
+
height: `${rowBounds.size}px`,
|
|
246
|
+
width: `${columnBounds.size}px`,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
columns.push({
|
|
250
|
+
key: columnIndex,
|
|
251
|
+
columnIndex,
|
|
252
|
+
rowIndex,
|
|
253
|
+
style: cellStyle,
|
|
254
|
+
ariaAttributes: {
|
|
255
|
+
'aria-colindex': columnIndex + 1,
|
|
256
|
+
role: 'gridcell',
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
result.push({
|
|
262
|
+
key: rowIndex,
|
|
263
|
+
rowIndex,
|
|
264
|
+
columns,
|
|
265
|
+
ariaRowIndex: rowIndex + 1,
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return result
|
|
271
|
+
})
|
|
272
|
+
</script>
|
|
273
|
+
|
|
274
|
+
<template>
|
|
275
|
+
<component
|
|
276
|
+
:is="tag"
|
|
277
|
+
ref="element"
|
|
278
|
+
:class="$props.class"
|
|
279
|
+
:style="{
|
|
280
|
+
position: 'relative',
|
|
281
|
+
maxHeight: '100%',
|
|
282
|
+
maxWidth: '100%',
|
|
283
|
+
flexGrow: 1,
|
|
284
|
+
overflow: 'auto',
|
|
285
|
+
...style
|
|
286
|
+
}"
|
|
287
|
+
:dir="dir"
|
|
288
|
+
role="grid"
|
|
289
|
+
:aria-colcount="columnCount"
|
|
290
|
+
:aria-rowcount="rowCount"
|
|
291
|
+
>
|
|
292
|
+
<div
|
|
293
|
+
v-for="row in rows"
|
|
294
|
+
:key="row.key"
|
|
295
|
+
role="row"
|
|
296
|
+
:aria-rowindex="row.ariaRowIndex"
|
|
297
|
+
>
|
|
298
|
+
<template v-for="cell in row.columns" :key="cell.key">
|
|
299
|
+
<slot
|
|
300
|
+
name="cell"
|
|
301
|
+
:column-index="cell.columnIndex"
|
|
302
|
+
:row-index="cell.rowIndex"
|
|
303
|
+
:style="cell.style"
|
|
304
|
+
:aria-attributes="cell.ariaAttributes"
|
|
305
|
+
:props="cellProps"
|
|
306
|
+
></slot>
|
|
307
|
+
</template>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<slot ></slot>
|
|
311
|
+
|
|
312
|
+
<!-- Sizing element -->
|
|
313
|
+
<div
|
|
314
|
+
aria-hidden
|
|
315
|
+
:style="{
|
|
316
|
+
height: `${getEstimatedHeight}px`,
|
|
317
|
+
width: `${getEstimatedWidth}px`,
|
|
318
|
+
zIndex: -1
|
|
319
|
+
}"
|
|
320
|
+
></div>
|
|
321
|
+
</component>
|
|
322
|
+
</template>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { CSSProperties } from 'vue'
|
|
2
|
+
import type { TagNames } from '../../types'
|
|
3
|
+
|
|
4
|
+
type ForbiddenKeys = 'ariaAttributes' | 'columnIndex' | 'rowIndex' | 'style';
|
|
5
|
+
type ExcludeForbiddenKeys<Type> = {
|
|
6
|
+
[Key in keyof Type]: Key extends ForbiddenKeys ? never : Type[Key];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface VirtualGridProps {
|
|
10
|
+
/**
|
|
11
|
+
* Additional props to be passed to the cell-rendering component via slots.
|
|
12
|
+
*/
|
|
13
|
+
cellProps?: ExcludeForbiddenKeys<Record<string, unknown>>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CSS class name.
|
|
17
|
+
*/
|
|
18
|
+
class?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Number of columns to be rendered in the grid.
|
|
22
|
+
*/
|
|
23
|
+
columnCount: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Column width; the following formats are supported:
|
|
27
|
+
* - number of pixels (number)
|
|
28
|
+
* - percentage of the grid's current width (string)
|
|
29
|
+
* - function that returns the column width (in pixels) given an index and `cellProps`
|
|
30
|
+
*/
|
|
31
|
+
columnWidth:
|
|
32
|
+
| number
|
|
33
|
+
| string
|
|
34
|
+
| ((index: number, cellProps: Record<string, unknown>) => number);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default height of grid for initial render.
|
|
38
|
+
* This value is important for server rendering.
|
|
39
|
+
*/
|
|
40
|
+
defaultHeight?: number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Default width of grid for initial render.
|
|
44
|
+
* This value is important for server rendering.
|
|
45
|
+
*/
|
|
46
|
+
defaultWidth?: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Indicates the directionality of grid cells.
|
|
50
|
+
*/
|
|
51
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Callback notified when the range of rendered cells changes.
|
|
55
|
+
*/
|
|
56
|
+
onCellsRendered?: (
|
|
57
|
+
visibleCells: {
|
|
58
|
+
columnStartIndex: number;
|
|
59
|
+
columnStopIndex: number;
|
|
60
|
+
rowStartIndex: number;
|
|
61
|
+
rowStopIndex: number;
|
|
62
|
+
},
|
|
63
|
+
allCells: {
|
|
64
|
+
columnStartIndex: number;
|
|
65
|
+
columnStopIndex: number;
|
|
66
|
+
rowStartIndex: number;
|
|
67
|
+
rowStopIndex: number;
|
|
68
|
+
}
|
|
69
|
+
) => void;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Callback notified when the Grid's outermost HTMLElement resizes.
|
|
73
|
+
* This may be used to (re)scroll a cell into view.
|
|
74
|
+
*/
|
|
75
|
+
onResize?: (
|
|
76
|
+
size: { height: number; width: number; },
|
|
77
|
+
prevSize: { height: number; width: number; }
|
|
78
|
+
) => void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* How many additional rows/columns to render outside of the visible area.
|
|
82
|
+
* This can reduce visual flickering near the edges of a grid when scrolling.
|
|
83
|
+
*/
|
|
84
|
+
overscanCount?: number;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Number of rows to be rendered in the grid.
|
|
88
|
+
*/
|
|
89
|
+
rowCount: number;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Row height; the following formats are supported:
|
|
93
|
+
* - number of pixels (number)
|
|
94
|
+
* - percentage of the grid's current height (string)
|
|
95
|
+
* - function that returns the row height (in pixels) given an index and `cellProps`
|
|
96
|
+
*/
|
|
97
|
+
rowHeight: number | string | ((index: number, cellProps: Record<string, unknown>) => number);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Optional CSS properties.
|
|
101
|
+
* The grid of cells will fill the height and width defined by this style.
|
|
102
|
+
*/
|
|
103
|
+
style?: CSSProperties;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Can be used to override the root HTML element rendered by the Grid component.
|
|
107
|
+
* The default value is "div", meaning that Grid renders an HTMLDivElement as its root.
|
|
108
|
+
*/
|
|
109
|
+
tag?: TagNames;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CellSlotProps {
|
|
113
|
+
ariaAttributes: {
|
|
114
|
+
'aria-colindex': number;
|
|
115
|
+
role: 'gridcell';
|
|
116
|
+
};
|
|
117
|
+
columnIndex: number;
|
|
118
|
+
rowIndex: number;
|
|
119
|
+
style: CSSProperties;
|
|
120
|
+
props?: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Imperative Grid API.
|
|
125
|
+
*/
|
|
126
|
+
export interface VirtualGridImperativeAPI {
|
|
127
|
+
/**
|
|
128
|
+
* Outermost HTML element for the grid if mounted and null (if not mounted.
|
|
129
|
+
*/
|
|
130
|
+
readonly element: HTMLDivElement | null;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Scrolls the grid so that the specified cell is visible.
|
|
134
|
+
*/
|
|
135
|
+
scrollToCell(config: {
|
|
136
|
+
behavior?: ScrollBehavior;
|
|
137
|
+
columnAlign?: 'auto' | 'center' | 'end' | 'smart' | 'start';
|
|
138
|
+
columnIndex: number;
|
|
139
|
+
rowAlign?: 'auto' | 'center' | 'end' | 'smart' | 'start';
|
|
140
|
+
rowIndex: number;
|
|
141
|
+
}): void;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Scrolls the grid so that the specified column is visible.
|
|
145
|
+
*/
|
|
146
|
+
scrollToColumn(config: {
|
|
147
|
+
align?: 'auto' | 'center' | 'end' | 'smart' | 'start';
|
|
148
|
+
behavior?: ScrollBehavior;
|
|
149
|
+
index: number;
|
|
150
|
+
}): void;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Scrolls the grid so that the specified row is visible.
|
|
154
|
+
*/
|
|
155
|
+
scrollToRow(config: {
|
|
156
|
+
align?: 'auto' | 'center' | 'end' | 'smart' | 'start';
|
|
157
|
+
behavior?: ScrollBehavior;
|
|
158
|
+
index: number;
|
|
159
|
+
}): void;
|
|
160
|
+
}
|