@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.
Files changed (156) hide show
  1. package/README.md +2 -2
  2. package/docs/assets/css/tailwind.css +6 -0
  3. package/docs/components/common/CodePreview.vue +14 -9
  4. package/docs/components/common/DocsFeatures.vue +41 -0
  5. package/docs/components/common/DocsHero.vue +216 -0
  6. package/docs/components/common/DocumentPage.vue +99 -112
  7. package/docs/components/common/ExampleBlocks.vue +157 -0
  8. package/docs/components/menu/DocsMenu.vue +3 -0
  9. package/docs/components/toolbar/Toolbar.vue +11 -2
  10. package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
  11. package/docs/components/toolbar/ToolbarSearch.vue +59 -62
  12. package/docs/composables/useDocMeta.ts +47 -0
  13. package/docs/icons.ts +28 -0
  14. package/docs/layouts/default.vue +1 -3
  15. package/docs/layouts/simple.vue +3 -1
  16. package/docs/main.ts +5 -0
  17. package/docs/pages/colors.vue +56 -47
  18. package/docs/pages/component/infiniteLoader/composable.vue +168 -0
  19. package/docs/pages/component/infiniteLoader/index.vue +36 -0
  20. package/docs/pages/component/infiniteLoader/usage.vue +161 -0
  21. package/docs/pages/component/select/size.vue +1 -1
  22. package/docs/pages/component/select/usage.vue +14 -7
  23. package/docs/pages/component/virtualGrid/index.vue +29 -0
  24. package/docs/pages/component/virtualGrid/usage.vue +20 -0
  25. package/docs/pages/component/virtualList/dynamicHeight.vue +75 -0
  26. package/docs/pages/component/virtualList/index.vue +36 -0
  27. package/docs/pages/component/virtualList/usage.vue +17 -0
  28. package/docs/pages/error.vue +5 -3
  29. package/docs/pages/icons.vue +64 -54
  30. package/docs/pages/index.vue +93 -82
  31. package/docs/pages/typography.vue +38 -28
  32. package/docs/router/index.ts +31 -3
  33. package/docs/search/components.json +1 -1
  34. package/docs/search/index.json +1 -0
  35. package/lib/components/container/theme/Container.base.theme.js +1 -1
  36. package/lib/components/divider/theme/Divider.base.theme.js +1 -1
  37. package/lib/components/input/Input.vue.js +23 -24
  38. package/lib/components/select/Select.vue.d.ts +16 -27
  39. package/lib/components/select/Select.vue.js +452 -345
  40. package/lib/components/table/Table.vue.js +1 -1
  41. package/lib/composables/useVirtualList.d.ts +1 -1
  42. package/lib/index.d.ts +1 -0
  43. package/lib/index.js +88 -76
  44. package/lib/index.umd.js +4 -4
  45. package/lib/install.js +15 -7
  46. package/lib/version.d.ts +1 -1
  47. package/lib/version.js +1 -1
  48. package/lib/virtual/components/infiniteLoader/InfiniteLoader.test.d.ts +1 -0
  49. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.d.ts +49 -0
  50. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue.js +21 -0
  51. package/lib/virtual/components/infiniteLoader/InfiniteLoader.vue2.js +4 -0
  52. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.d.ts +185 -0
  53. package/lib/virtual/components/virtualGrid/VirtualGrid.vue.js +241 -0
  54. package/lib/virtual/components/virtualGrid/VirtualGrid.vue2.js +4 -0
  55. package/lib/virtual/components/virtualGrid/types.d.ts +138 -0
  56. package/lib/virtual/components/virtualList/VirtualList.test.d.ts +1 -0
  57. package/lib/virtual/components/virtualList/VirtualList.vue.d.ts +135 -0
  58. package/lib/virtual/components/virtualList/VirtualList.vue.js +159 -0
  59. package/lib/virtual/components/virtualList/VirtualList.vue2.js +4 -0
  60. package/lib/virtual/components/virtualList/isDynamicRowHeight.d.ts +2 -0
  61. package/lib/virtual/components/virtualList/isDynamicRowHeight.js +6 -0
  62. package/lib/virtual/components/virtualList/types.d.ts +115 -0
  63. package/lib/virtual/components/virtualList/useDynamicRowHeight.d.ts +7 -0
  64. package/lib/virtual/components/virtualList/useDynamicRowHeight.js +68 -0
  65. package/lib/virtual/components/virtualList/useDynamicRowHeight.test.d.ts +1 -0
  66. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.d.ts +8 -0
  67. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.js +41 -0
  68. package/lib/virtual/composables/infinite-loader/scanForUnloadedIndices.test.d.ts +1 -0
  69. package/lib/virtual/composables/infinite-loader/types.d.ts +30 -0
  70. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.d.ts +6 -0
  71. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.js +42 -0
  72. package/lib/virtual/composables/infinite-loader/useInfiniteLoader.test.d.ts +1 -0
  73. package/lib/virtual/core/createCachedBounds.d.ts +6 -0
  74. package/lib/virtual/core/createCachedBounds.js +55 -0
  75. package/lib/virtual/core/getEstimatedSize.d.ts +6 -0
  76. package/lib/virtual/core/getEstimatedSize.js +22 -0
  77. package/lib/virtual/core/getOffsetForIndex.d.ts +11 -0
  78. package/lib/virtual/core/getOffsetForIndex.js +40 -0
  79. package/lib/virtual/core/getStartStopIndices.d.ts +13 -0
  80. package/lib/virtual/core/getStartStopIndices.js +31 -0
  81. package/lib/virtual/core/getStartStopIndices.test.d.ts +1 -0
  82. package/lib/virtual/core/types.d.ts +11 -0
  83. package/lib/virtual/core/useCachedBounds.d.ts +7 -0
  84. package/lib/virtual/core/useCachedBounds.js +18 -0
  85. package/lib/virtual/core/useIsRtl.d.ts +2 -0
  86. package/lib/virtual/core/useIsRtl.js +15 -0
  87. package/lib/virtual/core/useItemSize.d.ts +5 -0
  88. package/lib/virtual/core/useItemSize.js +27 -0
  89. package/lib/virtual/core/useVirtualizer.d.ts +33 -0
  90. package/lib/virtual/core/useVirtualizer.js +171 -0
  91. package/lib/virtual/index.d.ts +9 -0
  92. package/lib/virtual/test-utils/mockResizeObserver.d.ts +15 -0
  93. package/lib/virtual/types.d.ts +2 -0
  94. package/lib/virtual/utils/adjustScrollOffsetForRtl.d.ts +7 -0
  95. package/lib/virtual/utils/adjustScrollOffsetForRtl.js +24 -0
  96. package/lib/virtual/utils/areArraysEqual.d.ts +1 -0
  97. package/lib/virtual/utils/assert.d.ts +1 -0
  98. package/lib/virtual/utils/assert.js +7 -0
  99. package/lib/virtual/utils/getRTLOffsetType.d.ts +2 -0
  100. package/lib/virtual/utils/getRTLOffsetType.js +13 -0
  101. package/lib/virtual/utils/getScrollbarSize.d.ts +2 -0
  102. package/lib/virtual/utils/getScrollbarSize.js +11 -0
  103. package/lib/virtual/utils/isRtl.d.ts +1 -0
  104. package/lib/virtual/utils/isRtl.js +12 -0
  105. package/lib/virtual/utils/parseNumericStyleValue.d.ts +2 -0
  106. package/lib/virtual/utils/parseNumericStyleValue.js +15 -0
  107. package/lib/virtual/utils/shallowCompare.d.ts +1 -0
  108. package/lib/virtual/utils/shallowCompare.js +14 -0
  109. package/package.json +8 -3
  110. package/src/components/container/theme/Container.base.theme.ts +1 -1
  111. package/src/components/divider/theme/Divider.base.theme.ts +1 -1
  112. package/src/components/input/Input.vue +1 -2
  113. package/src/components/select/Select.vue +97 -20
  114. package/src/components/table/Table.vue +1 -1
  115. package/src/composables/useVirtualList.ts +1 -1
  116. package/src/index.ts +1 -0
  117. package/src/install.ts +9 -3
  118. package/src/version.ts +1 -1
  119. package/src/virtual/README.md +285 -0
  120. package/src/virtual/components/infiniteLoader/InfiniteLoader.test.ts +96 -0
  121. package/src/virtual/components/infiniteLoader/InfiniteLoader.vue +18 -0
  122. package/src/virtual/components/virtualGrid/VirtualGrid.vue +322 -0
  123. package/src/virtual/components/virtualGrid/types.ts +160 -0
  124. package/src/virtual/components/virtualList/VirtualList.test.ts +164 -0
  125. package/src/virtual/components/virtualList/VirtualList.vue +227 -0
  126. package/src/virtual/components/virtualList/isDynamicRowHeight.ts +13 -0
  127. package/src/virtual/components/virtualList/types.ts +127 -0
  128. package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +197 -0
  129. package/src/virtual/components/virtualList/useDynamicRowHeight.ts +149 -0
  130. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.test.ts +141 -0
  131. package/src/virtual/composables/infinite-loader/scanForUnloadedIndices.ts +82 -0
  132. package/src/virtual/composables/infinite-loader/types.ts +36 -0
  133. package/src/virtual/composables/infinite-loader/useInfiniteLoader.test.ts +236 -0
  134. package/src/virtual/composables/infinite-loader/useInfiniteLoader.ts +88 -0
  135. package/src/virtual/core/createCachedBounds.ts +72 -0
  136. package/src/virtual/core/getEstimatedSize.ts +29 -0
  137. package/src/virtual/core/getOffsetForIndex.ts +90 -0
  138. package/src/virtual/core/getStartStopIndices.test.ts +45 -0
  139. package/src/virtual/core/getStartStopIndices.ts +71 -0
  140. package/src/virtual/core/types.ts +17 -0
  141. package/src/virtual/core/useCachedBounds.ts +21 -0
  142. package/src/virtual/core/useIsRtl.ts +25 -0
  143. package/src/virtual/core/useItemSize.ts +34 -0
  144. package/src/virtual/core/useVirtualizer.ts +294 -0
  145. package/src/virtual/index.ts +25 -0
  146. package/src/virtual/test-utils/mockResizeObserver.ts +162 -0
  147. package/src/virtual/types.ts +3 -0
  148. package/src/virtual/utils/adjustScrollOffsetForRtl.ts +37 -0
  149. package/src/virtual/utils/areArraysEqual.ts +13 -0
  150. package/src/virtual/utils/assert.ts +10 -0
  151. package/src/virtual/utils/getRTLOffsetType.ts +51 -0
  152. package/src/virtual/utils/getScrollbarSize.ts +24 -0
  153. package/src/virtual/utils/isRtl.ts +13 -0
  154. package/src/virtual/utils/parseNumericStyleValue.ts +21 -0
  155. package/src/virtual/utils/shallowCompare.ts +29 -0
  156. package/volar.d.ts +3 -0
@@ -0,0 +1,164 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { mount, flushPromises } from '@vue/test-utils'
3
+ import { h, nextTick, type CSSProperties } from 'vue'
4
+ import VirtualList from './VirtualList.vue'
5
+ import { useDynamicRowHeight, DATA_ATTRIBUTE_LIST_INDEX } from './useDynamicRowHeight'
6
+ import { mockResizeObserver, setElementSize } from '../../test-utils/mockResizeObserver'
7
+ import type { DynamicRowHeight } from './types'
8
+
9
+ interface RowSlotProps {
10
+ index: number;
11
+ style: CSSProperties;
12
+ }
13
+
14
+ const listStyle = { height: '400px' } as const
15
+
16
+ function mountVirtualList(
17
+ props: Record<string, unknown>,
18
+ dynamicRowHeight?: DynamicRowHeight,
19
+ ) {
20
+ return mount(VirtualList, {
21
+ props: {
22
+ rowCount: 100,
23
+ rowHeight: dynamicRowHeight ?? 50,
24
+ style: listStyle,
25
+ ...props,
26
+ },
27
+ slots: {
28
+ row: ({ index, style }: RowSlotProps) =>
29
+ h('div', { style, class: 'test-row' }, `Row ${index}`),
30
+ },
31
+ })
32
+ }
33
+
34
+ function createMockDynamicRowHeight(defaultRowHeight = 50) {
35
+ const unobserve = vi.fn<[], void>()
36
+
37
+ const dynamicRowHeight: DynamicRowHeight = {
38
+ getAverageRowHeight: vi.fn(() => defaultRowHeight),
39
+ getRowHeight: vi.fn(() => undefined),
40
+ setRowHeight: vi.fn(),
41
+ observeRowElements: vi.fn(() => unobserve),
42
+ cleanup: vi.fn(),
43
+ }
44
+
45
+ return { dynamicRowHeight, unobserve }
46
+ }
47
+
48
+ describe('VirtualList', () => {
49
+ it('renders correctly', () => {
50
+ const wrapper = mountVirtualList({})
51
+
52
+ expect(wrapper.exists()).toBe(true)
53
+ expect(wrapper.attributes('role')).toBe('list')
54
+ })
55
+
56
+ it('renders visible rows', () => {
57
+ const wrapper = mountVirtualList({})
58
+
59
+ const rows = wrapper.findAll('.test-row')
60
+
61
+ expect(rows.length).toBeGreaterThan(0)
62
+ })
63
+
64
+ describe('dynamic row height watch', () => {
65
+ it('does not observe row elements when rowHeight is fixed', async () => {
66
+ const { dynamicRowHeight } = createMockDynamicRowHeight()
67
+
68
+ mountVirtualList({ rowHeight: 50 })
69
+
70
+ await flushPromises()
71
+ await nextTick()
72
+
73
+ expect(dynamicRowHeight.observeRowElements).not.toHaveBeenCalled()
74
+ })
75
+
76
+ it('observes visible row elements and sets data-virtual-index', async () => {
77
+ const { dynamicRowHeight } = createMockDynamicRowHeight()
78
+
79
+ const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
80
+
81
+ await flushPromises()
82
+ await nextTick()
83
+
84
+ expect(dynamicRowHeight.observeRowElements).toHaveBeenCalled()
85
+
86
+ const observedElements = vi.mocked(dynamicRowHeight.observeRowElements).mock
87
+ .calls[0]?.[0] as Element[]
88
+
89
+ expect(observedElements.length).toBeGreaterThan(0)
90
+ expect(
91
+ observedElements.every((element) => !element.hasAttribute('aria-hidden')),
92
+ ).toBe(true)
93
+
94
+ const listElement = wrapper.element as HTMLElement
95
+ const rowElements = Array.from(listElement.children).filter(
96
+ (child) => !child.hasAttribute('aria-hidden'),
97
+ )
98
+
99
+ expect(rowElements.length).toBe(observedElements.length)
100
+ rowElements.forEach((element) => {
101
+ expect(element.hasAttribute(DATA_ATTRIBUTE_LIST_INDEX)).toBe(true)
102
+ })
103
+ })
104
+
105
+ it('runs previous unobserve when the observed range changes', async () => {
106
+ const { dynamicRowHeight, unobserve } = createMockDynamicRowHeight()
107
+
108
+ const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
109
+
110
+ await flushPromises()
111
+ await nextTick()
112
+
113
+ expect(dynamicRowHeight.observeRowElements).toHaveBeenCalledTimes(1)
114
+ expect(unobserve).not.toHaveBeenCalled()
115
+
116
+ const listElement = wrapper.element as HTMLDivElement
117
+
118
+ listElement.scrollTop = 2500
119
+ listElement.dispatchEvent(new Event('scroll'))
120
+ await flushPromises()
121
+ await nextTick()
122
+
123
+ expect(unobserve).toHaveBeenCalledTimes(1)
124
+ expect(dynamicRowHeight.observeRowElements).toHaveBeenCalledTimes(2)
125
+ })
126
+ })
127
+
128
+ describe('dynamic row height integration', () => {
129
+ let unmockResizeObserver: (() => void) | undefined
130
+ let dynamicRowHeight: DynamicRowHeight | undefined
131
+
132
+ beforeEach(() => {
133
+ unmockResizeObserver = mockResizeObserver()
134
+ dynamicRowHeight = useDynamicRowHeight({ defaultRowHeight: 50 })
135
+ })
136
+
137
+ afterEach(() => {
138
+ dynamicRowHeight?.cleanup()
139
+ unmockResizeObserver?.()
140
+ })
141
+
142
+ it('updates measured heights when observed rows resize', async () => {
143
+ const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
144
+
145
+ await flushPromises()
146
+ await nextTick()
147
+
148
+ const rowElement = wrapper.find('.test-row').element as HTMLElement
149
+
150
+ setElementSize({
151
+ element: rowElement,
152
+ width: 100,
153
+ height: 72,
154
+ })
155
+
156
+ await flushPromises()
157
+ await nextTick()
158
+
159
+ const index = Number(rowElement.getAttribute(DATA_ATTRIBUTE_LIST_INDEX))
160
+
161
+ expect(dynamicRowHeight?.getRowHeight(index)).toBe(72)
162
+ })
163
+ })
164
+ })
@@ -0,0 +1,227 @@
1
+ <script lang="ts">
2
+ export default {
3
+ name: 'XVirtualList',
4
+ }
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, ref, watch, type CSSProperties } from 'vue'
9
+ import { useVirtualizer } from '../../core/useVirtualizer'
10
+ import type { Align } from '../../types'
11
+ import { isDynamicRowHeight as isDynamicRowHeightUtil } from './isDynamicRowHeight'
12
+ import type { VirtualListProps, VirtualListImperativeAPI, DynamicRowHeight } from './types'
13
+ import { DATA_ATTRIBUTE_LIST_INDEX } from './useDynamicRowHeight'
14
+
15
+ const props = withDefaults(defineProps<VirtualListProps>(), {
16
+ defaultHeight: 0,
17
+ overscanCount: 3,
18
+ tag: 'div',
19
+ })
20
+
21
+ const element = ref<HTMLDivElement | null>(null)
22
+
23
+ const rowProps = computed(() => props.rowProps || ({} as Record<string, unknown>))
24
+
25
+ const itemCount = computed(() => props.rowCount)
26
+
27
+ const isDynamicRowHeight = computed(() => isDynamicRowHeightUtil(props.rowHeight))
28
+
29
+ const rowHeight = computed<number | string | ((index: number, cellProps: Record<string, unknown>) => number)>(() => {
30
+ if (isDynamicRowHeight.value) {
31
+ const dynamicHeight = props.rowHeight as DynamicRowHeight
32
+ const avgHeight = dynamicHeight.getAverageRowHeight()
33
+
34
+ return (index: number) => {
35
+ return (
36
+ dynamicHeight.getRowHeight(index) ??
37
+ avgHeight
38
+ )
39
+ }
40
+ }
41
+
42
+ return props.rowHeight as number | string | ((index: number, cellProps: Record<string, unknown>) => number)
43
+ })
44
+
45
+ const {
46
+ getCellBounds,
47
+ getEstimatedSize,
48
+ scrollToIndex,
49
+ startIndexOverscan,
50
+ startIndexVisible,
51
+ stopIndexOverscan,
52
+ stopIndexVisible,
53
+ } = useVirtualizer({
54
+ containerElement: element,
55
+ containerStyle: props.style,
56
+ defaultContainerSize: props.defaultHeight,
57
+ direction: 'vertical',
58
+ itemCount,
59
+ itemProps: rowProps,
60
+ itemSize: rowHeight,
61
+ onResize: props.onResize,
62
+ overscanCount: props.overscanCount,
63
+ })
64
+
65
+ // Expose imperative API
66
+ defineExpose<VirtualListImperativeAPI>({
67
+ get element() {
68
+ return element.value
69
+ },
70
+
71
+ scrollToRow({
72
+ align = 'auto',
73
+ behavior = 'auto',
74
+ index,
75
+ }: {
76
+ align?: Align;
77
+ behavior?: ScrollBehavior;
78
+ index: number;
79
+ }) {
80
+ const top = scrollToIndex({
81
+ align,
82
+ containerScrollOffset: element.value?.scrollTop ?? 0,
83
+ index,
84
+ })
85
+
86
+ if (typeof element.value?.scrollTo === 'function' && top !== undefined) {
87
+ element.value.scrollTo({
88
+ behavior,
89
+ top,
90
+ })
91
+ }
92
+ },
93
+ })
94
+
95
+ // Watch for dynamic row heights - run after DOM updates
96
+ watch(
97
+ [element, startIndexOverscan, stopIndexOverscan, isDynamicRowHeight, () => props.rowHeight],
98
+ ([el, start, stop, isDynamic], _old, onCleanup) => {
99
+ if (!el || !isDynamic) {
100
+ return
101
+ }
102
+
103
+ const rows = Array.from(el.children).filter((item, index) => {
104
+ if (item.hasAttribute('aria-hidden')) {
105
+ // Ignore sizing element
106
+ return false
107
+ }
108
+
109
+ const attribute = `${start + index}`
110
+
111
+ item.setAttribute(DATA_ATTRIBUTE_LIST_INDEX, attribute)
112
+
113
+ return true
114
+ })
115
+
116
+ const dynamicHeight = props.rowHeight as DynamicRowHeight
117
+
118
+ onCleanup(dynamicHeight.observeRowElements(rows))
119
+ },
120
+ { flush: 'post' }, // Run after DOM updates
121
+ )
122
+
123
+ // Notify when visible rows change
124
+ watch(
125
+ [startIndexOverscan, startIndexVisible, stopIndexOverscan, stopIndexVisible],
126
+ ([startOverscan, startVisible, stopOverscan, stopVisible]) => {
127
+ if (startOverscan >= 0 && stopOverscan >= 0 && props.onRowsRendered) {
128
+ props.onRowsRendered(
129
+ {
130
+ startIndex: startVisible,
131
+ stopIndex: stopVisible,
132
+ },
133
+ {
134
+ startIndex: startOverscan,
135
+ stopIndex: stopOverscan,
136
+ },
137
+ )
138
+ }
139
+ },
140
+ )
141
+
142
+ interface RowData {
143
+ key: number;
144
+ index: number;
145
+ style: CSSProperties;
146
+ ariaAttributes: {
147
+ 'aria-posinset': number;
148
+ 'aria-setsize': number;
149
+ role: 'listitem';
150
+ };
151
+ }
152
+
153
+ // Generate rows
154
+ const rows = computed(() => {
155
+ const result: RowData[] = []
156
+
157
+ if (props.rowCount > 0) {
158
+ for (
159
+ let index = startIndexOverscan.value;
160
+ index <= stopIndexOverscan.value;
161
+ index++
162
+ ) {
163
+ const bounds = getCellBounds(index)
164
+
165
+ const rowStyle: CSSProperties = {
166
+ position: 'absolute',
167
+ left: 0,
168
+ transform: `translateY(${bounds.scrollOffset}px)`,
169
+ // In case of dynamic row heights, don't specify a height style
170
+ height: isDynamicRowHeight.value ? undefined : `${bounds.size}px`,
171
+ width: '100%',
172
+ }
173
+
174
+ result.push({
175
+ key: index,
176
+ index,
177
+ style: rowStyle,
178
+ ariaAttributes: {
179
+ 'aria-posinset': index + 1,
180
+ 'aria-setsize': props.rowCount,
181
+ role: 'listitem',
182
+ },
183
+ })
184
+ }
185
+ }
186
+
187
+ return result
188
+ })
189
+ </script>
190
+
191
+ <template>
192
+ <component
193
+ :is="tag"
194
+ ref="element"
195
+ :class="$props.class"
196
+ :style="{
197
+ position: 'relative',
198
+ maxHeight: '100%',
199
+ flexGrow: 1,
200
+ overflowY: 'auto',
201
+ ...style
202
+ }"
203
+ role="list"
204
+ >
205
+ <template v-for="row in rows" :key="row.key">
206
+ <slot
207
+ name="row"
208
+ :index="row.index"
209
+ :style="row.style"
210
+ :aria-attributes="row.ariaAttributes"
211
+ :props="rowProps"
212
+ ></slot>
213
+ </template>
214
+
215
+ <slot ></slot>
216
+
217
+ <!-- Sizing element -->
218
+ <div
219
+ aria-hidden
220
+ :style="{
221
+ height: `${getEstimatedSize}px`,
222
+ width: '100%',
223
+ zIndex: -1
224
+ }"
225
+ ></div>
226
+ </component>
227
+ </template>
@@ -0,0 +1,13 @@
1
+ import type { DynamicRowHeight } from './types'
2
+
3
+ export function isDynamicRowHeight(
4
+ value: unknown,
5
+ ): value is DynamicRowHeight {
6
+ return (
7
+ typeof value === 'object' &&
8
+ value !== null &&
9
+ 'getAverageRowHeight' in value &&
10
+ 'getRowHeight' in value &&
11
+ 'setRowHeight' in value
12
+ )
13
+ }
@@ -0,0 +1,127 @@
1
+ import type { CSSProperties } from 'vue'
2
+ import type { TagNames } from '../../types'
3
+
4
+ export type DynamicRowHeight = {
5
+ getAverageRowHeight(): number;
6
+ getRowHeight(index: number): number | undefined;
7
+ setRowHeight(index: number, size: number): void;
8
+ observeRowElements: (elements: Element[] | NodeListOf<Element>) => () => void;
9
+ cleanup: () => void;
10
+ };
11
+
12
+ type ForbiddenKeys = 'ariaAttributes' | 'index' | 'style';
13
+ type ExcludeForbiddenKeys<Type> = {
14
+ [Key in keyof Type]: Key extends ForbiddenKeys ? never : Type[Key];
15
+ };
16
+
17
+ export interface VirtualListProps {
18
+ /**
19
+ * CSS class name.
20
+ */
21
+ class?: string;
22
+
23
+ /**
24
+ * Default height of list for initial render.
25
+ * This value is important for server rendering.
26
+ */
27
+ defaultHeight?: number;
28
+
29
+ /**
30
+ * Callback notified when the List's outermost HTMLElement resizes.
31
+ * This may be used to (re)scroll a row into view.
32
+ */
33
+ onResize?: (
34
+ size: { height: number; width: number; },
35
+ prevSize: { height: number; width: number; }
36
+ ) => void;
37
+
38
+ /**
39
+ * Callback notified when the range of visible rows changes.
40
+ */
41
+ onRowsRendered?: (
42
+ visibleRows: { startIndex: number; stopIndex: number; },
43
+ allRows: { startIndex: number; stopIndex: number; }
44
+ ) => void;
45
+
46
+ /**
47
+ * How many additional rows to render outside of the visible area.
48
+ * This can reduce visual flickering near the edges of a list when scrolling.
49
+ */
50
+ overscanCount?: number;
51
+
52
+ /**
53
+ * Number of items to be rendered in the list.
54
+ */
55
+ rowCount: number;
56
+
57
+ /**
58
+ * Row height; the following formats are supported:
59
+ * - number of pixels (number)
60
+ * - percentage of the grid's current height (string)
61
+ * - function that returns the row height (in pixels) given an index and `cellProps`
62
+ * - dynamic row height cache returned by the `useDynamicRowHeight` hook
63
+ *
64
+ * ⚠️ Dynamic row heights are not as efficient as predetermined sizes.
65
+ * It's recommended to provide your own height values if they can be determined ahead of time.
66
+ */
67
+ rowHeight:
68
+ | number
69
+ | string
70
+ | ((index: number, cellProps: Record<string, unknown>) => number)
71
+ | DynamicRowHeight;
72
+
73
+ /**
74
+ * Additional props to be passed to the row-rendering component via slots.
75
+ */
76
+ rowProps?: ExcludeForbiddenKeys<Record<string, unknown>>;
77
+
78
+ /**
79
+ * Optional CSS properties.
80
+ * The list of rows will fill the height defined by this style.
81
+ */
82
+ style?: CSSProperties;
83
+
84
+ /**
85
+ * Can be used to override the root HTML element rendered by the List component.
86
+ * The default value is "div", meaning that List renders an HTMLDivElement as its root.
87
+ *
88
+ * ⚠️ In most use cases the default ARIA roles are sufficient and this prop is not needed.
89
+ */
90
+ tag?: TagNames;
91
+ }
92
+
93
+ export interface RowSlotProps {
94
+ ariaAttributes: {
95
+ 'aria-posinset': number;
96
+ 'aria-setsize': number;
97
+ role: 'listitem';
98
+ };
99
+ index: number;
100
+ style: CSSProperties;
101
+ props?: Record<string, unknown>;
102
+ }
103
+
104
+ /**
105
+ * Imperative List API.
106
+ */
107
+ export interface VirtualListImperativeAPI {
108
+ /**
109
+ * Outermost HTML element for the list if mounted and null (if not mounted.
110
+ */
111
+ readonly element: HTMLDivElement | null;
112
+
113
+ /**
114
+ * Scrolls the list so that the specified row is visible.
115
+ *
116
+ * @param align Determines the vertical alignment of the element within the list
117
+ * @param behavior Determines whether scrolling is instant or animates smoothly
118
+ * @param index Index of the row to scroll to (0-based)
119
+ *
120
+ * @throws RangeError if an invalid row index is provided
121
+ */
122
+ scrollToRow(config: {
123
+ align?: 'auto' | 'center' | 'end' | 'smart' | 'start';
124
+ behavior?: 'auto' | 'instant' | 'smooth';
125
+ index: number;
126
+ }): void;
127
+ }