@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
package/docs/pages/colors.vue
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
import { useColors, useNotifications, colors as TailwindColors } from '@indielayer/ui'
|
|
4
|
+
import { useDocMeta } from '../composables/useDocMeta'
|
|
5
|
+
|
|
6
|
+
useDocMeta({
|
|
7
|
+
title: 'Colors',
|
|
8
|
+
description: 'Color palette, semantic tokens, and color props on Indielayer UI components.',
|
|
9
|
+
path: '/colors',
|
|
10
|
+
})
|
|
4
11
|
|
|
5
12
|
const notifications = useNotifications()
|
|
6
13
|
const { getPalette } = useColors()
|
|
@@ -23,44 +30,45 @@ function copyPalette(val: any) {
|
|
|
23
30
|
</script>
|
|
24
31
|
|
|
25
32
|
<template>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<div
|
|
38
|
-
|
|
33
|
+
<div class="docs-container">
|
|
34
|
+
<h1 class="text-4xl font-semibold">Colors</h1>
|
|
35
|
+
<h2 class="text-lg my-2 text-gray-500 dark:text-gray-400">Generate your own palettes, use Tailwind's or use color prop to generate at runtime</h2>
|
|
36
|
+
<x-divider class="mt-4 mb-8"/>
|
|
37
|
+
|
|
38
|
+
<h2 class="text-2xl mb-2">Generate your own palettes</h2>
|
|
39
|
+
<p class="mb-8 text-gray-500 dark:text-gray-400">Generate your own color palette like Tailwind. With the same properties as Tailwind color palettes.</p>
|
|
40
|
+
<p class="font-medium"></p>
|
|
41
|
+
<div class="mt-4">
|
|
42
|
+
<x-input v-model="color500" label="Change here the middle value (500) to generate a new palette"/>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="grid grid-cols-11 my-4 text-sm">
|
|
45
|
+
<div v-for="shade in Object.keys(palette)" :key="shade" class="h-32 flex items-center justify-center" :style="`background-color: ${palette[shade]}`">{{ shade }}</div>
|
|
46
|
+
</div>
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
<x-button icon-left="copy" :color="color500" block @click="copyPalette(palette)">Copy Palette</x-button>
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
<p class="text-gray-500 dark:text-gray-400 mt-4">This is the generated palette when you use the color directly on the color prop like <x-button <b>color="#10B981"</b>>Hello</x-button></p>
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
<x-divider class="mt-4 mb-8"/>
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
<h2 class="text-2xl mb-2">Theme colors</h2>
|
|
55
|
+
<p class="mb-8 text-gray-500 dark:text-gray-400">Default theme color names and how to customize them.</p>
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
<p>The default theme color names are
|
|
58
|
+
<b class="text-primary-500">primary</b>,
|
|
59
|
+
<b class="text-secondary-500">secondary</b>,
|
|
60
|
+
<b class="text-success-500">success</b>,
|
|
61
|
+
<b class="text-warning-500">warning</b> and
|
|
62
|
+
<b class="text-error-500">error</b>.
|
|
63
|
+
You can customize them or even add your own.
|
|
64
|
+
</p>
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
<p class="my-4"></p>
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
<p class="my-2">Customize the color prop, e.g. <b>color="primary"</b> on the UI initialization.</p>
|
|
69
|
+
<code-snippet
|
|
70
|
+
lang="js"
|
|
71
|
+
:code="`import { createApp } from 'vue'
|
|
64
72
|
import UI, { colors } from '@indielayer/ui'
|
|
65
73
|
|
|
66
74
|
const app = createApp(App)
|
|
@@ -76,14 +84,14 @@ app.use(UI, {
|
|
|
76
84
|
},
|
|
77
85
|
}
|
|
78
86
|
})`"
|
|
79
|
-
|
|
87
|
+
/>
|
|
80
88
|
|
|
81
|
-
|
|
89
|
+
<p class="my-4">Allow you to use the Tailwind classes like <b>text-primary-500</b>, <b>bg-primary-500</b>, etc:</p>
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
<div>
|
|
92
|
+
<code-snippet
|
|
93
|
+
lang="js"
|
|
94
|
+
:code="`// tailwind.config.js
|
|
87
95
|
//...
|
|
88
96
|
const colors = require('tailwindcss/colors')
|
|
89
97
|
|
|
@@ -101,19 +109,20 @@ module.exports = {
|
|
|
101
109
|
},
|
|
102
110
|
},
|
|
103
111
|
}`"
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
106
114
|
|
|
107
|
-
|
|
115
|
+
<x-divider class="mt-4 mb-8"/>
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
<h2 class="text-2xl mb-2">Tailwind Colors</h2>
|
|
118
|
+
<p class="mb-8 text-gray-500 dark:text-gray-400">Default tailwind color palettes.</p>
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
<div class="grid gap-6 grid-cols-1 md:grid-cols-2 text-xs">
|
|
121
|
+
<div v-for="(pal, key) in TailwindColors" :key="key">
|
|
122
|
+
<div class="text-overline mb-2">{{ key }}</div>
|
|
123
|
+
<div class="grid grid-cols-11">
|
|
124
|
+
<div v-for="(color, palKey) in pal" :key="palKey" class="h-32 flex items-center justify-center" :style="`background-color: ${color}`">{{ palKey }}</div>
|
|
125
|
+
</div>
|
|
117
126
|
</div>
|
|
118
127
|
</div>
|
|
119
128
|
</div>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
import { useInfiniteLoader } from '@indielayer/ui'
|
|
4
|
+
|
|
5
|
+
// Infinite loader example
|
|
6
|
+
const infiniteItems = ref<string[]>([])
|
|
7
|
+
const hasMoreData = ref(true)
|
|
8
|
+
const isLoading = ref(false)
|
|
9
|
+
const highestLoadedIndex = ref(-1)
|
|
10
|
+
const apiCursor = ref<string | null>(null) // Cursor from last API call
|
|
11
|
+
|
|
12
|
+
// Track the actual loaded count
|
|
13
|
+
const loadedCount = computed(() => {
|
|
14
|
+
return infiniteItems.value.filter((item) => item !== undefined).length
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const isRowLoaded = (index: number) => {
|
|
18
|
+
// If loading, pretend ALL rows are loaded to prevent useInfiniteLoader
|
|
19
|
+
// from marking new rows as pending while a request is in progress
|
|
20
|
+
if (isLoading.value) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The last row is the loading indicator when we have more data
|
|
25
|
+
const isLoadingRow = hasMoreData.value && index === infiniteItems.value.length
|
|
26
|
+
|
|
27
|
+
if (isLoadingRow) {
|
|
28
|
+
return false // Not loaded yet, trigger loading
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return infiniteItems.value[index] !== undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Helper to get item or loading indicator
|
|
35
|
+
const getItem = (index: number) => {
|
|
36
|
+
// If this is the loading indicator row
|
|
37
|
+
if (hasMoreData.value && index === infiniteItems.value.length) {
|
|
38
|
+
return isLoading.value ? '⏳ Loading more...' : '↓ Scroll for more'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return infiniteItems.value[index]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const loadMoreRows = async (startIndex: number, stopIndex: number) => {
|
|
45
|
+
// Block all new requests if one is already in progress
|
|
46
|
+
if (isLoading.value) {
|
|
47
|
+
console.log(`[loadMoreRows] Already loading, rejecting request for ${startIndex}-${stopIndex}`)
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isLoading.value = true
|
|
53
|
+
const currentLength = infiniteItems.value.length
|
|
54
|
+
|
|
55
|
+
console.log(`[loadMoreRows] Loading batch of 50 items, cursor: ${apiCursor.value}, current items: ${currentLength}`)
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Simulate API call with cursor
|
|
59
|
+
// In real implementation: const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
61
|
+
|
|
62
|
+
// In a real implementation, you'd use the cursor and response data:
|
|
63
|
+
// const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
|
|
64
|
+
// const { items, nextCursor, hasMore } = await response.json();
|
|
65
|
+
// apiCursor.value = nextCursor;
|
|
66
|
+
// hasMoreData.value = hasMore;
|
|
67
|
+
//
|
|
68
|
+
// Then append the items from the API:
|
|
69
|
+
// const newItems = [...infiniteItems.value, ...items];
|
|
70
|
+
// infiniteItems.value = newItems;
|
|
71
|
+
|
|
72
|
+
// Simulate API response: generate 50 items
|
|
73
|
+
const batchSize = 50
|
|
74
|
+
const itemsToAdd = []
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < batchSize; i++) {
|
|
77
|
+
const itemIndex = currentLength + i
|
|
78
|
+
|
|
79
|
+
itemsToAdd.push(`Item ${itemIndex + 1}`)
|
|
80
|
+
if (itemIndex > highestLoadedIndex.value) {
|
|
81
|
+
highestLoadedIndex.value = itemIndex
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Simulate updating cursor based on response
|
|
86
|
+
apiCursor.value = `cursor_after_${currentLength + batchSize - 1}`
|
|
87
|
+
|
|
88
|
+
// Append all new items at once
|
|
89
|
+
infiniteItems.value = [...infiniteItems.value, ...itemsToAdd]
|
|
90
|
+
|
|
91
|
+
console.log(`[loadMoreRows] Loaded ${batchSize} items, total: ${infiniteItems.value.length}, cursor: ${apiCursor.value}`)
|
|
92
|
+
|
|
93
|
+
// Check if we've reached the limit (simulate end of data)
|
|
94
|
+
if (infiniteItems.value.length >= 500) {
|
|
95
|
+
hasMoreData.value = false
|
|
96
|
+
console.log('[loadMoreRows] Reached limit, no more data to load')
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[loadMoreRows] Error loading items:', error)
|
|
100
|
+
} finally {
|
|
101
|
+
isLoading.value = false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const infiniteRowCount = computed(() => {
|
|
106
|
+
const itemsLength = infiniteItems.value.length
|
|
107
|
+
|
|
108
|
+
if (!hasMoreData.value) {
|
|
109
|
+
// No more data, show only loaded items
|
|
110
|
+
return itemsLength
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Show loaded items + 1 loading indicator row
|
|
114
|
+
return itemsLength + 1
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const { onRowsRendered } = useInfiniteLoader(
|
|
118
|
+
computed(() => {
|
|
119
|
+
const rc = infiniteRowCount.value
|
|
120
|
+
|
|
121
|
+
console.log(`[useInfiniteLoader] Props updated - rowCount: ${rc}, loaded: ${loadedCount.value}`)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
isRowLoaded,
|
|
125
|
+
loadMoreRows,
|
|
126
|
+
rowCount: rc,
|
|
127
|
+
minimumBatchSize: 50, // Load 50 items per batch
|
|
128
|
+
threshold: 10, // Start loading when within 10 rows of needing data
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// Load first batch of items on mount or when switching to infinite tab
|
|
134
|
+
const loadInitialItems = async () => {
|
|
135
|
+
if (infiniteItems.value.length === 0) {
|
|
136
|
+
await loadMoreRows(0, 49) // Indices are ignored, always loads 50 items
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onMounted(() => {
|
|
141
|
+
loadInitialItems()
|
|
142
|
+
})
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<template>
|
|
146
|
+
<p class="info">
|
|
147
|
+
Loaded {{ loadedCount }} of {{ infiniteRowCount }} items
|
|
148
|
+
<span v-if="isLoading" class="loading"> (Loading...)</span>
|
|
149
|
+
</p>
|
|
150
|
+
<x-virtual-list
|
|
151
|
+
:row-count="infiniteRowCount"
|
|
152
|
+
:row-height="50"
|
|
153
|
+
class="h-96 border border-gray-200 rounded-md bg-white"
|
|
154
|
+
:on-rows-rendered="onRowsRendered"
|
|
155
|
+
>
|
|
156
|
+
<template #row="{ index, style }">
|
|
157
|
+
<div
|
|
158
|
+
:style="style"
|
|
159
|
+
:class="[
|
|
160
|
+
'h-12 flex items-center justify-center border-b border-gray-200',
|
|
161
|
+
{ 'loading-indicator': index === infiniteItems.length && hasMoreData }
|
|
162
|
+
]"
|
|
163
|
+
>
|
|
164
|
+
{{ getItem(index) }}
|
|
165
|
+
</div>
|
|
166
|
+
</template>
|
|
167
|
+
</x-virtual-list>
|
|
168
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { XInfiniteLoader } from '@indielayer/ui'
|
|
3
|
+
import UsageDemoCode from './usage.vue?raw'
|
|
4
|
+
import UsageDemo from './usage.vue'
|
|
5
|
+
import ComposableDemoCode from './composable.vue?raw'
|
|
6
|
+
import ComposableDemo from './composable.vue'
|
|
7
|
+
|
|
8
|
+
const title = 'InfiniteLoader'
|
|
9
|
+
const description = 'InfiniteLoader is a component that allows you to load data on demand as users scroll through a list.'
|
|
10
|
+
const components = [XInfiniteLoader]
|
|
11
|
+
const demos = [{
|
|
12
|
+
name: 'Usage',
|
|
13
|
+
description: '',
|
|
14
|
+
code: UsageDemoCode,
|
|
15
|
+
component: UsageDemo,
|
|
16
|
+
}, {
|
|
17
|
+
name: 'Composable',
|
|
18
|
+
description: '',
|
|
19
|
+
code: ComposableDemoCode,
|
|
20
|
+
component: ComposableDemo,
|
|
21
|
+
}]
|
|
22
|
+
const back = ''
|
|
23
|
+
const next = ''
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<document-page
|
|
28
|
+
github="https://github.com/indielayer/ui/blob/main/packages/ui/docs/pages/component/infiniteLoader"
|
|
29
|
+
:title="title"
|
|
30
|
+
:description="description"
|
|
31
|
+
:components="components"
|
|
32
|
+
:demos="demos"
|
|
33
|
+
:back="back"
|
|
34
|
+
:next="next"
|
|
35
|
+
/>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
// Infinite loader example
|
|
5
|
+
const infiniteItems = ref<string[]>([])
|
|
6
|
+
const hasMoreData = ref(true)
|
|
7
|
+
const isLoading = ref(false)
|
|
8
|
+
const highestLoadedIndex = ref(-1)
|
|
9
|
+
const apiCursor = ref<string | null>(null) // Cursor from last API call
|
|
10
|
+
|
|
11
|
+
// Track the actual loaded count
|
|
12
|
+
const loadedCount = computed(() => {
|
|
13
|
+
return infiniteItems.value.filter((item) => item !== undefined).length
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const isRowLoaded = (index: number) => {
|
|
17
|
+
// If loading, pretend ALL rows are loaded to prevent useInfiniteLoader
|
|
18
|
+
// from marking new rows as pending while a request is in progress
|
|
19
|
+
if (isLoading.value) {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The last row is the loading indicator when we have more data
|
|
24
|
+
const isLoadingRow = hasMoreData.value && index === infiniteItems.value.length
|
|
25
|
+
|
|
26
|
+
if (isLoadingRow) {
|
|
27
|
+
return false // Not loaded yet, trigger loading
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return infiniteItems.value[index] !== undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to get item or loading indicator
|
|
34
|
+
const getItem = (index: number) => {
|
|
35
|
+
// If this is the loading indicator row
|
|
36
|
+
if (hasMoreData.value && index === infiniteItems.value.length) {
|
|
37
|
+
return isLoading.value ? '⏳ Loading more...' : '↓ Scroll for more'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return infiniteItems.value[index]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const loadMoreRows = async (startIndex: number, stopIndex: number) => {
|
|
44
|
+
// Block all new requests if one is already in progress
|
|
45
|
+
if (isLoading.value) {
|
|
46
|
+
console.log(`[loadMoreRows] Already loading, rejecting request for ${startIndex}-${stopIndex}`)
|
|
47
|
+
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
isLoading.value = true
|
|
52
|
+
const currentLength = infiniteItems.value.length
|
|
53
|
+
|
|
54
|
+
console.log(`[loadMoreRows] Loading batch of 50 items, cursor: ${apiCursor.value}, current items: ${currentLength}`)
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Simulate API call with cursor
|
|
58
|
+
// In real implementation: const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
60
|
+
|
|
61
|
+
// In a real implementation, you'd use the cursor and response data:
|
|
62
|
+
// const response = await fetch(`/api/items?cursor=${apiCursor.value}&limit=50`);
|
|
63
|
+
// const { items, nextCursor, hasMore } = await response.json();
|
|
64
|
+
// apiCursor.value = nextCursor;
|
|
65
|
+
// hasMoreData.value = hasMore;
|
|
66
|
+
//
|
|
67
|
+
// Then append the items from the API:
|
|
68
|
+
// const newItems = [...infiniteItems.value, ...items];
|
|
69
|
+
// infiniteItems.value = newItems;
|
|
70
|
+
|
|
71
|
+
// Simulate API response: generate 50 items
|
|
72
|
+
const batchSize = 50
|
|
73
|
+
const itemsToAdd = []
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < batchSize; i++) {
|
|
76
|
+
const itemIndex = currentLength + i
|
|
77
|
+
|
|
78
|
+
itemsToAdd.push(`Item ${itemIndex + 1}`)
|
|
79
|
+
if (itemIndex > highestLoadedIndex.value) {
|
|
80
|
+
highestLoadedIndex.value = itemIndex
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Simulate updating cursor based on response
|
|
85
|
+
apiCursor.value = `cursor_after_${currentLength + batchSize - 1}`
|
|
86
|
+
|
|
87
|
+
// Append all new items at once
|
|
88
|
+
infiniteItems.value = [...infiniteItems.value, ...itemsToAdd]
|
|
89
|
+
|
|
90
|
+
console.log(`[loadMoreRows] Loaded ${batchSize} items, total: ${infiniteItems.value.length}, cursor: ${apiCursor.value}`)
|
|
91
|
+
|
|
92
|
+
// Check if we've reached the limit (simulate end of data)
|
|
93
|
+
if (infiniteItems.value.length >= 500) {
|
|
94
|
+
hasMoreData.value = false
|
|
95
|
+
console.log('[loadMoreRows] Reached limit, no more data to load')
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('[loadMoreRows] Error loading items:', error)
|
|
99
|
+
} finally {
|
|
100
|
+
isLoading.value = false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const infiniteRowCount = computed(() => {
|
|
105
|
+
const itemsLength = infiniteItems.value.length
|
|
106
|
+
|
|
107
|
+
if (!hasMoreData.value) {
|
|
108
|
+
// No more data, show only loaded items
|
|
109
|
+
return itemsLength
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Show loaded items + 1 loading indicator row
|
|
113
|
+
return itemsLength + 1
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Load first batch of items on mount or when switching to infinite tab
|
|
117
|
+
const loadInitialItems = async () => {
|
|
118
|
+
if (infiniteItems.value.length === 0) {
|
|
119
|
+
await loadMoreRows(0, 49) // Indices are ignored, always loads 50 items
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onMounted(() => {
|
|
124
|
+
loadInitialItems()
|
|
125
|
+
})
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<p class="info">
|
|
130
|
+
Loaded {{ loadedCount }} of {{ infiniteRowCount }} items
|
|
131
|
+
<span v-if="isLoading" class="loading"> (Loading...)</span>
|
|
132
|
+
</p>
|
|
133
|
+
<x-infinite-loader
|
|
134
|
+
:is-row-loaded="isRowLoaded"
|
|
135
|
+
:load-more-rows="loadMoreRows"
|
|
136
|
+
:row-count="infiniteRowCount"
|
|
137
|
+
:minimum-batch-size="50"
|
|
138
|
+
:threshold="10"
|
|
139
|
+
>
|
|
140
|
+
<template #default="{ onRowsRendered }">
|
|
141
|
+
<x-virtual-list
|
|
142
|
+
:row-count="infiniteRowCount"
|
|
143
|
+
:row-height="50"
|
|
144
|
+
class="h-96 border border-gray-200 rounded-md bg-white"
|
|
145
|
+
:on-rows-rendered="onRowsRendered"
|
|
146
|
+
>
|
|
147
|
+
<template #row="{ index, style }">
|
|
148
|
+
<div
|
|
149
|
+
:style="style"
|
|
150
|
+
:class="[
|
|
151
|
+
'h-12 flex items-center justify-center border-b border-gray-200',
|
|
152
|
+
{ 'loading-indicator': index === infiniteItems.length && hasMoreData }
|
|
153
|
+
]"
|
|
154
|
+
>
|
|
155
|
+
{{ getItem(index) }}
|
|
156
|
+
</div>
|
|
157
|
+
</template>
|
|
158
|
+
</x-virtual-list>
|
|
159
|
+
</template>
|
|
160
|
+
</x-infinite-loader>
|
|
161
|
+
</template>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
+
import type { SelectOption } from '@indielayer/ui'
|
|
3
4
|
|
|
4
|
-
const selected = ref<
|
|
5
|
-
const selected2 = ref<
|
|
6
|
-
const options = ref([
|
|
5
|
+
const selected = ref<string>()
|
|
6
|
+
const selected2 = ref<string>()
|
|
7
|
+
const options = ref<SelectOption[]>([
|
|
7
8
|
{ value: 'A', label: 'Option A' },
|
|
8
9
|
{ value: 'B', label: 'Option B' },
|
|
9
10
|
{ value: 'C', label: 'Option C' },
|
|
@@ -11,16 +12,16 @@ const options = ref([
|
|
|
11
12
|
|
|
12
13
|
// function to generate
|
|
13
14
|
function genOptions(x: number) {
|
|
14
|
-
const options = []
|
|
15
|
+
const options: SelectOption[] = []
|
|
15
16
|
|
|
16
17
|
for (let i = 0; i < x; i++) {
|
|
17
|
-
options.push({ value: i.toString(), label: 'Option ' + i, suffix: i })
|
|
18
|
+
options.push({ value: i.toString(), label: 'Option ' + i, prefix: 'bg-green-500', suffix: 'dude' + i.toString() })
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
return options
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
const options2 = ref(genOptions(1000))
|
|
24
|
+
const options2 = ref<SelectOption[]>(genOptions(1000))
|
|
24
25
|
</script>
|
|
25
26
|
|
|
26
27
|
<template>
|
|
@@ -39,13 +40,18 @@ const options2 = ref(genOptions(1000))
|
|
|
39
40
|
label="Filterable - virtual list"
|
|
40
41
|
placeholder="Placeholder"
|
|
41
42
|
filterable
|
|
43
|
+
filterable-prefix
|
|
44
|
+
filterable-suffix
|
|
42
45
|
clearable
|
|
43
46
|
virtual-list
|
|
44
47
|
:virtual-list-item-height="33"
|
|
45
48
|
:options="options2"
|
|
46
49
|
>
|
|
50
|
+
<template #prefix="{ item }">
|
|
51
|
+
<div class="w-2 h-2 shrink-0 rounded-full text-xs" :class="item.prefix"></div>
|
|
52
|
+
</template>
|
|
47
53
|
<template #suffix="{ item }">
|
|
48
|
-
<span class="text-secondary-400 text-xs font-mono
|
|
54
|
+
<span class="text-secondary-400 text-xs font-mono">#{{ item.suffix }}</span>
|
|
49
55
|
</template>
|
|
50
56
|
</x-select>
|
|
51
57
|
<x-select
|
|
@@ -57,6 +63,7 @@ const options2 = ref(genOptions(1000))
|
|
|
57
63
|
>
|
|
58
64
|
<template #input="{ popover, label, disabled }">
|
|
59
65
|
<button
|
|
66
|
+
type="button"
|
|
60
67
|
class="w-full text-left border rounded-md px-3 py-2"
|
|
61
68
|
:disabled="disabled"
|
|
62
69
|
@click="popover?.show()"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { XVirtualGrid } from '@indielayer/ui'
|
|
3
|
+
import UsageDemoCode from './usage.vue?raw'
|
|
4
|
+
import UsageDemo from './usage.vue'
|
|
5
|
+
|
|
6
|
+
const title = 'VirtualGrid'
|
|
7
|
+
const description = 'VirtualGrid is a component that allows you to render a grid of items in a virtualized way. It is a high-performance component that is used to render large grids of items.'
|
|
8
|
+
const components = [XVirtualGrid]
|
|
9
|
+
const demos = [{
|
|
10
|
+
name: 'Usage',
|
|
11
|
+
description: '',
|
|
12
|
+
code: UsageDemoCode,
|
|
13
|
+
component: UsageDemo,
|
|
14
|
+
}]
|
|
15
|
+
const back = 'virtualList'
|
|
16
|
+
const next = ''
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<document-page
|
|
21
|
+
github="https://github.com/indielayer/ui/blob/main/packages/ui/docs/pages/component/virtualGrid"
|
|
22
|
+
:title="title"
|
|
23
|
+
:description="description"
|
|
24
|
+
:components="components"
|
|
25
|
+
:demos="demos"
|
|
26
|
+
:back="back"
|
|
27
|
+
:next="next"
|
|
28
|
+
/>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const gridRowCount = 1000
|
|
3
|
+
const gridColumnCount = 1000
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<x-virtual-grid
|
|
8
|
+
:row-count="gridRowCount"
|
|
9
|
+
:column-count="gridColumnCount"
|
|
10
|
+
:row-height="48"
|
|
11
|
+
:column-width="100"
|
|
12
|
+
class="h-96 border border-gray-200 rounded-md bg-white w-full"
|
|
13
|
+
>
|
|
14
|
+
<template #cell="{ rowIndex, columnIndex, style }">
|
|
15
|
+
<div :style="style" class="h-12 flex items-center justify-center border border-gray-200">
|
|
16
|
+
R{{ rowIndex }}, C{{ columnIndex }}
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
</x-virtual-grid>
|
|
20
|
+
</template>
|